2009年2月24日星期二

C Traps and Pitfalls--1(词法陷阱)

     本章探讨的是符号和组成符号的字符之间的关系,以及有关符号含义的一些常见的误解。
     当我们阅读一个句子时,我们并不去考虑组成这个句子的单词中单个字母的含义,而是把单词作为一个整体来理解。确实,字母本身并没有什么意义,我们总是将字母组成单词,然后给单词赋予一定的意义。对于C语言或其他语言编写的程序,道理也是一样的。
     编译器中负责将程序分解为一个一个符号的那部分,一般称为”词法分析器”,在C语言中符号之间的空白将被忽略,包括空格符,制表符和换行符。

=和==
     一般而言,更为常用的操作用更为简短的表达,以节省大家的时间。故符号=作为赋值,符号==作为比较运算,因为赋值比使用的更为频繁。
     =的左边的操作数为左值(lvalue),右边的为右值(rvalue),关于lvalue和rvalue,详情请参阅《The C programming Language》(K&R/TCPL)的附录A.5和A.7。
     潜在的问题代码:
    if (x = y)
            foo();

     程序员的本意是判断(x == y)还是直接的赋值(x = y)呢?现在的gcc编译器在发现形如e1 = e2的表达式出现在条件判断的部分时,会给出相关的警告信息,如果编译的时候把警告打开的话。
    比如下面这段代码:
    #include <stdio.h>
    #include <stdlib.h>
    
    int main(int argc, char **argv)
    {
            int x = 1;
            int y = 1;
            if ( x = y)
                printf(“x = y is true\n”);
            exit(0);
    }

    不打开警告的编译方法和打开警告的编译方法分别为:
    gcc -o prog prog.c
    gcc -Wall -o prog prog.c

    则可以看到如果打开警告,编译器会有提示”warning: suggest parentheses around assignment used as truth value”,如果不打开警告的话,就没有任何警告提示了。
    作为一种解决方法和一种良好的编程习惯,作者建议写作显式比较的形式:
    if ((x = y) != 0)
            foo();

    既能去除警告,也使得代码的意图一目了然,只是多了几个字符,何乐而不为!

词法分析的“贪心法”
    C语言的符号可以是单字符号,形如/,*和=,也可以是多字符符号,形如/*和==,当编译器读入一个'/'后又跟着一个'*',那么编译器是如何判断的呢?法则就是贪心法,即编译器从左到右一个字符一个字符的读入,如果该字符可能组成一个符号,那么再读入下一个字符,判断已经读入的两个字符组成的字符串是否可能是一个符号的组成部分;如果可能继续读入下一个字符,重复上述判断,直到读入的字符组成的字符串已经不再可能组成一个有意义的符号。这个处理策略一般称作“贪心法”,有时也叫“大嘴法”,一句话概括来说,就是每个符号应该包括尽可能多的字符。Kernighan和Ritchie在《The C programming Language》 (K&R/TCPL)的附录A.2.1对这个方法这样描述:
    If the input stream has been separated into tokens up to a given character, the next token is the longest string of characters that could constitute a token.

    如果到某一个为止的输入流被分割成若干记号,那么,下一个记号就是后续字符序列中可能构成记号的最长的字符串。
    但是我们要注意到,虽然编译器使用的是贪心法,但是也要遵循基本的词法规则,比如lvalue和rvalue的概念,这章中的练习1-4就是a+++++b的含义是什么,这里应该使用贪心法,不过也需要考虑到lvalue和rvalue的概念,不然很容易想当然的出错,第八章中给出的答案也只是说a++的结果不能作为lvalue,编译器不接受a++作为后面的++运算的操作数,只是说明了一下,没有系统的说明出处,一会我在详细的说明一下。

字符与字符串
    这个相对容易理解多了,单引号和双引号嘛。下面说说两者的实质的区别:
    单引号:单引号字符本质上是代表一个整数,其值对应于该字符在编译器采用的字符集中的序列值。对于普通的采用ASCII字符集的编译器,如gcc,'a'就是97或0141(八进制)。
    双引号:双引号的字符串代表的是一个指向无名数组起始字符的指针,该数组被双引号之间的字符以及一个额外的二进制值为零的字符'\0'初始化。
   
    下面的语句:
    printf("Hello world\n");

   
    char hello[] = {'H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '\n', '\0'}
    printf(hello);

    两者是等价的。
    这个很好理解,用一种形而上学的理解记忆,一个串相当于N多字符的组合,要放在一起使用,当然只能用数组的形式了,也就是说串就是字符的无名数组形式了,既然无名,就只能使用指针来引用了,串就是这个指针而这个数组最后的'\0'代表串的结束(总得有一个东西来代表结束吧)。

    关于这个结束字符串的东西,在《Expert C Programming: Deep C Secrets》(《C专家编程》)中的第二章的2.1小节的小启发中说是NUL,而NULL代表空指针,表示什么也不指向,而NULLL则说明需要检查一下是不是拼写错误了。这里的NUL其实ASCII码表0值的char字面意思,NUL并没有在标准C中定义,当然如果自己想用的话,#define一下也不错。

    一句话总结就是,单引号的字符代表一个整数,双引号的则代表一个指针,混用的话编译器的类型检查功能会检测到这样的错误的,gcc就可以。
    #include <stdio.h>
    #include <stdlib.h>
    
    int main(int argc, char **argv)
    {
        printf('\n');
        exit(0);
    }


    使用gcc编译情况如下(不加-Wall参数):
    $ gcc -o prog prog.c
    prog.c: In function ‘main’:
    prog.c:6: warning: passing argument 1 of ‘printf’ makes pointer from integer without a cast

    运行情况如下:
    $ ./prog
    Segmentation fault

    因为标准C并没有指出编译器在类似错误的情况下的行为,所以常常是这种段错误了。

    另外一个就是类似char c = 'yes'的编译器行为了,gcc是后一个字符直接覆盖前一个字符,所以c就是's'了,gcc也会有警告的:

    $ cat prog.c
    #include <stdio.h>
    #include <stdlib.h>
    
    int main(int argc, char **argv)
    {
        char c = 'yes';
        printf("%c\n", c);
        exit(0);
    }

    编译情况如下:
    $ gcc -o prog prog.c
    prog.c:6:11: warning: multi-character character constant
    prog.c: In function ‘main’:
    prog.c:6: warning: overflow in implicit constant conversion
    运行结果:
    $ ./prog
    s

这章最后
    这一章的最后,还是来说说上面提到的练习1-4的a+++++b。
    按照贪心法,似乎可以理解为((a++)++)+b,不过书中的答案说a++不能作为lvalue,因此编译器不会接受a++作为后面++运算符俄操作数,为什么不能作为左值,出处在哪里?

    出处就在
《The C programming Language》(K&R/TCPL)的附录
A.7.3.4 Postfix Incrementation ... ... The result is not an lvalue.
当然前缀的也不能作为lvalue,出处就是
A.7.4.1 Prefix Incrementation Operators ... ... The result is not an lvalue. ^_^

2009年2月22日星期日

C Traps and Pitfalls--0(导读)

      冲动前言:C Traps and Pitfalls一直呆在硬盘上数载,当我开始读的时候,发现却只是1985年的那篇29页的论文,当然里面有些关于编译器的东西都改过了,所以有些Traps and Pitfalls都不存在了。于是开始看这本较新的2002中文版本,里面第0章导读的的第三个例子,就是那个数组赋值循环的例子,让我来了兴趣,遂有了此笔记系列。

      关于书籍:一本讲述C语言的书,自出版以来,历经14载,它一直都被各个书评站点(或书评人)列入“重点推荐”的清单中。尤为夸张的是,14年来,在它的18次印刷版本中,除去第二次印刷稍微修改过一些问题,以后的16次印刷,我们居然发现它的内容没有丝毫的变更!!!对于技术书籍,我想其精确性与权威性也算是奇迹了吧。

      关于作者:Andrew Koenig是AT&T大规模程序研发部(前贝尔实验室)成员。他从1986年开始从事C语言的研究,1977年加入贝尔实验室。他编写了一些早期的类库,并在1988年组织召开了第一个完全意义上的C++会议。在ISO/ANSI C++委员会成立的1989年,他就加入了该委员会,并一直担任项目编辑。他已经发表了C++方面的100多篇论文,在Addsion-Wesley出版了C Trap and Pitfalls,还应邀到世界各地演讲。他的妻子Barbara Moo现任AT&T网络体系结构部门负责人。在1983年加入贝尔实验室不久,她开始从事Fortran 77编译器的研究工作,这是第一个用C++编写的商业产品。她负责AT&T的C++编译器项目直到AT&T转让出软件开发业务。她还负责指导SIGS会议、Lund技术学院和Stanford大学。Andrew Koenig和Barbara Moo堪称C++研究领域的“第一神仙眷侣”,他们不光有着多年的C++开发、研究和教学经验,而且还亲身参与了C++的演化和变革,是对C++的变化和发展起到重要影响的人。他们两人还曾经合著了Accelerated C++: Practical Programming by Example。

      作者的第一个C程序:
#include <stdio.h>

main()
{
        printf("Hello world");
}

      执行结果却如下所示:
% cc prog.c
% a.out (注意,现在应该使用./a.out来运行这个默认的gcc编译出来的a.out了)
Hello world%

      这个结果怎么看起来和自己很多时候的那么相似呢,因为没有加上换行符'\n',所以那个unix提示符号'%'紧跟着world出来了。以后自己也要注意。

      第二个C程序例子,书上的是部分代码,我将代码补全如下:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
        int i;
        int a[10];

        for (i = 0; i <= 10; i++) {
                a[i] = 0;
                printf("a[%d] = 0\n", i);
        }

        exit(0);
}

      使用gcc编译(gcc -o array array.c)并运行这个程序,运行发现会是一个死循环!虽然我知道应该是i < 10,9是数组的界限,但是却不知道为什么i <= 10的时候会是死循环,这个让我提起了兴趣。我尝试这使用gcc -Wall -o array array.c来编译程序,以为gcc会有警告,却发现任何警告信息!答案作者是在第三章3.6节讨论的。当然,我要继续往下看。

      我很赞同作者提出的"程序设计错误实际上反应的是程序与程序员对该程序的'心智模式'两者的相异之处",赞同Howard Gardner在《心灵的新科学》(The Mind's New Science)中的观点:人们的心智模式决定了人们如何认识周遭的世界。

      从较低的层面考察,程序是由符号(token)序列组成,犹如一本书是由一个一个的单词所组成的一样,将程序分解为token的过程,称为"词法分析"。
      组成程序的这些token,又可以看成是语句和声明的序列,犹如一本书可以看成是由单词进一步结合而成的句子的集合一样,符号(token)和单词如何组成更大的单元,组成什么样的语义,取决于语法细节,理解了语法细节,还需要理解语义细节才行。
      程序的连接,预处理等,也是现在我的认知空白,接着看书吧。


2009年2月8日星期日

fedora的gba模拟器VisualBoyAdvance教程

前言:
最近在手机上玩了两天的《口袋妖怪》,gba掌机的,也想在电脑上试试看,起始以为街机就能搞定,后来发现自己的游戏知识真实贫乏啊,两种完全不同的平台!在学习中发现gba在pc上的模拟器叫VisualBoyAdvance!

Visual Boy Advance - Gamboy Advance Emulator
VBA is the best and most popular Gameboy Advance emulator around. Emulates GBA, GBC, SGB, GBA roms! Supports ZIP-ed roms, so that after downloading files from the net you don't even have to un-zip them.
VBA主页是http://vba.ngemu.com/

当然要是有前端界面不就更好,它就在这里VBA Express:
What's VBA Express? It's First, to configure VisualBoyAdvance (emulator)... EASILY! Graphics, sound, controls (keyboard and joystick), paths (to saves and captures)... You can configure all this without edit VisualBoyAdvance.cfg manually!
VBA Express主页:http://vbaexpress.tuxfamily.org/english.php

安装:
在VBA Express主页:http://vbaexpress.tuxfamily.org/english.php我们可以下载:
visualboyadvance-1.7.2-1.FC4.i386.rpm http://vbaexpress.tuxfamily.org/visu...1.FC4.i386.rpm
vbaexpress-1.2-1.i386.rpm http://vbaexpress.tuxfamily.org/vbae...1.2-1.i386.rpm
之后自己安装就可以了。

使用:
在程序菜单-->games-->VBA Express打开,然后File-->open a game-->选择自己的gba rom即可。初始键盘为Z, X, Enter, 上下左右键,其他的设置可以自己看看。

Click the image to open in full size.
Click the image to open in full size.
Click the image to open in full size.

fedora街机模拟器xmame+gxmame安装使用教程

前言:我们主要使用xmame+gxmame来做模拟器,游戏可以使用neogeo的rom和mame自己的rom等。

1.下载xmame
xmame的下载(xmame-0.106-1.fc6.i386.rpm):
RPM Information for xmame-0.106-1.fc6.i386.rpm页面:http://sulphur.freshrpms.net/rpm.html?id=348
下载地址:http://ftp.freshrpms.net/pub/freshrp...1.fc6.i386.rpm
我看了一下,似乎就只有fc6的,我的系统是fc9,没问题的。

2.下载gxmame
下载地址:ftp://rpmfind.net/linux/freshrpms/fe...2.fc4.i386.rpm
说明:仓库有gmame 0.22版本,这个版本不好,我安装了,很不好,bug很多,建议使用上面的0.35,不知道0.35是否有人打包更高系统版本,不过这个是可以用的。

3.下载neo bios等文件
使用neogeo bios 方便大家用mame模拟 Neogeo rom游戏,另外还有cpzn1和cpzn2的bios。
这里解压neogeo.7z即可得到neogeo.zip

4.roms路径以及相关文件
xmame默认的roms文件路径是/usr/share/xmame/roms,我们要把neogeo.zip, cpzn1.zip, cpzn2.zip放在这个roms文件夹里面,然后就是游戏的rom了,比如kof97.

5.终端中的配置使用
在终端直接使用
代码:
xmame kof97
来运行游戏,使用
代码:
Alt + Home/Pgup
来放大一点。使用
代码:
Alt + Pgup + PgDn
来全屏和退出全屏,使用
代码:
 Esc
退出。

第一次运行游戏的时候,提示按“ok”,那么请按键盘的"o"和"k"即可进入游戏。然后按"Tab"键来进行键盘设置,然后选择"Input ( this Game ),因为xmame为每一个游戏保存一个配置文件。然后reset game一下,以后就可以直接使用了。
在图形界面中使用:在程序菜单中打开games->gxmame,界面比较简单,请大家自己配置即可,基本上可以不需要配置就直接使用。其他设置请自己摸索,或者参考其他文章。


cpzn1.zip.tar.bz2

cpzn2.zip.tar.bz2

neogeo.7z

Click the image to open in full size.
Click the image to open in full size.
Click the image to open in full size.

我们的忠告:仅供休闲娱乐,请勿沉迷;
我们的口号:在linux下玩游戏,是为了更好地学习linux!