Category Archives: 其它开发

关于C数组和指针的误解

我不知道大家是不是和我一样有这样的认识:一级指针和一维数组的语义行为是等价的,或者从编译器的角度看,两者是一样的。我想很少有人清楚编译器如何工作,又或许在实际工作中把两者当成同一物没有出现过问题,也就不会去细究两者的异同。

1. 什么是语义行为?

语义行为就是编译器用一系列操作对某个产生式(production rule)的解释。对于一个指针int *p,它的语义行为有:

  1. p所指向的元素类型是int;
  2. p变量本身有一个地址,其值为另一个合法的地址,两者并无关系。

另一方面,int p[]有两层含义:

  1. p 的类型是int[],对p不做越界检查;
  2. p的值就是p的地址(试试输出p&p,看看两者是否一样);
  3. p[2]的计算方法是*(p的值+2*sizeof(int)),注意这里说的是p的值。

如果表达式为int p[10],它相对int p[]要做越界检查。对于高维数组,其语义和一维的完全一样,只是地址计算上稍微繁琐。

2. 不区分一维数组和一维指针会导致问题吗?

一种常见的混用方式是:

void f( char *p )
{
    printf( %dn, p[2] );
}
int main()
{
    int v[] = {1, 2, 3};
    f( v );
    return 0;
}

这段程序不会有问题,因为参数传递是按值复制的,就是说p的值为v的值(就是v数组的地址)。但下面一段程序就要出错了:

A.c
int v[] = {1, 2, 3};
B.c:
int main() {
    extern int* v;
    printf( %dn, v[2] );
    return 0;
}

这段代码错误的原因在于:v作为数组时,其v的值语义为v的地址。现在v被告知是一个指针,于是v的值就是v的地址中所包含的值,上例中就是1v[2]相当于1[2],即取地址为3中的值,自然会segmentation fault

3. 二维数组和二级指针的若干问题

看下面的示例代码:

void f( int **v )

{

    printf( %dn, v[0][1] );

}
void g( int v[2][3] )

{

    printf( “%dn, v[0][2] );

}

int main()

{

    int v[][3] = { {1,2,3}, {3,4,5} };

    int **t = v;

    f( v );

    g( t );

    return 0;

}

很多人可能认为(包括我),二维数组的变量被编译器当做二级指针看待。其实这是错的,上例中f(v)的调用将会导致一个segmentation fault。根据数组的语义,int p[2][3]实际上是p指向一个int[2][3]类型的变量,据此来看看在f中发生的事情:v[0][1]实际上在尝试解析内存地址2,因为参数v的值就是传入数组的起始地址,所以v[0]就是1,然后解析地址1[1],固然导致错误。

那么,如何改造f,可以得到正确的输出呢?f可以如下实现:

void f( int **v )

{

    int (*t)[3] = v;

    printf( “%dn, *((t+1)[0]) );

}

但如果f在一个已经发布的函数库中(如getopt的第二个参数),那么我们便不能改动f。怎么办呢?此时就需要在调用f前做一件事情:

int **p;

p = (int**)malloc( sizeof(int) * 2 );
for ( i = 0; i < 2; ++i )
p[i] = (int*)malloc( sizeof(int) * 3 );

for ( i = 0; i < 2; ++i )
    for ( j = 0; j < 3; ++j )
p[i][j] = v[i][j];

f( p );

为什么这样?pv不同就在于数据没有连续存放,中间引入了一个转换层。因此,p的地址解析需要通过两步完成,而v只用一步。除此,p还比v多浪费了2void*的空间用于存放中间转换层数据。

上文中g(t)的调用是对的这个很容易看出,因为t只是起了个保存v的数据的作用,并没有做解析,然后将其传递给了类型匹配的参数。那用到t转换一次的意义在哪里呢?如果将g改造如下:

void g( int v[3][2])

{

    printf( “%dn, v[0][2] );

}

此时v的类型为int[3][2]而并非起初定义的int[2][3],程序仍然能编译通过。原因就在于用t做转储时,丢失了范围语义信息,所以再转换回来时编译器已经糊涂了,也只好默认它们能正常工作,只是释放一个warning而已。

以上可见,从语义的角度来学习一个语言,会在理解上有本质的提高。对于遇到的“奇怪”问题,多做些这样的分析才可以得到正确的解释。

自动测试工具第二版发布

距离上一次的发布已是一年有余,本来今年年初就打算要写,但后来不知为何放弃了计划,所以拖到现在,这也说明做事不得拖拉,越往后人越懒散。

好了,废话少说。新版本是完全重写的,因为上次的代码实在是不堪入目,因此结构性上有了很大的提高,有利于开发后续版本。功能上也做了大幅的增加,主要有:

1.

支持三种测试模式(两种数据对拍和一种标准OJ测试方式);
2.

自动探测输入数据和输出数据的后缀对照关系,不必再像POJ那样一定要命名为xx.in和xx.out;
3.

支持从源代码自动编译,不必再手工编译后传入;
4.

支持Special Judge功能;
5.

统计运行中程序的时间和内存占用。

其它的更多细节请参见readme.txt文件。

应该说,基本上功能上还是比较完善了,基于这个程序进一步开发OJ也不是难事。当然不足肯定是有的,比如内存占用的统计目前就不是非常准确,希望有谁能告诉我这个应该怎么做得更好。还有就是可移植性方面考虑很少,只针对Linux的32位平台进行了测试,MacOS和BSD等系统和64位平台没有做,而代码中也没有体现对这些平台特殊的支持,因此后续的改善工作还是有很多的。

上传的代码打的Tag是RC2版本,正式版的发布请继续关注这个帖子。

另外,还有什么功能上的需求,请大家在回复中不吝赐教,是否切合实际不用考虑,毕竟现在功能有限,我还打算继续开发。

附使用方法简要说明:

设当前目录下待测试的程序叫test.c,输入数据在input文件夹中,输出在output中:

输入:

bash > tester -m 3 -i test.c -I input -O output

输出:

Compile ./test.c ……. OK

Test 0 = Accepted, time = 40ms, mem = 1228KB

Test 1 = Accepted, time = 36ms, mem = 1212KB

Test 2 = Accepted, time = 32ms, mem = 1212KB

Test 3 = Accepted, time = 36ms, mem = 1216KB

Test 4 = Accepted, time = 36ms, mem = 1216KB

如果要对拍程序,设另外一个程序叫std.c,数据生成器为gen.c:

输入:

bash > tester -i quick_sort.c -s std.c -g gen.c

输出:

Compile ./quick_sort.c ……. OK

Compile ./std.c ……. OK

Compile ./gen.c ……. OK

Test 0 = Wrong Answer, time = 316ms, mem = 2280KB

注意,一旦某个数据有错误,就不再继续生成新数据,可以使用-D选项转储错误数据。

旧版不再提供下载。

————————————————————– 新版分割线 7/5/2010———————————————————————

其实早在09年1月,这个工具就已经基本完善。Final Version对以上所述的beta version改变很多,主要有:

1. 使用更简单,不必再有很复杂的命令行参数。如使用对拍模式,如下即可:

./tester -g data_gen.c prog1.c prog2.c

2. 代码经过几次重写,变得更加易读。由于2.0版本的开发目标是,使得OJ可以作为一个功能方便集成到其他程序,目前基本上实现。

其它介绍,情参见代码中的 readme.txt。

程序采用GPL 2.0协议发布,若重新发布,请表明原作者,谢谢。

下载:  tester

发布一个无线网络连接小工具

想必每个linux发行版都应该带了这样的小工具,我写这样一个小脚本的原因有3:

1. 字符界面,不必再进入X登录网络;

2. 避免每次都输入冗长的命令核密码;

3. 学习bash编程。

使用前请先保证当前用户有sudo使用网络界面控制命令的权利(iwconfig等),如果不行请先配置/etc/sudoer文件。

欢迎大家使用该工具,也欢迎大家提供自己的代码互相交流(回复留言即可)。

下载请点这里wuxian_ver1.0代码

=================================================

时隔一月,使用当中也发现了很多的bugs。针对这些,现发布过渡版本V1.5,以期解决如下问题:

1. 增加一些错误处理procedure;

2.  支持更多的功能,比如支持Ascii格式的password(这使得现有的密码保存文件的格式和原来的不兼容,建议手工在原有密码的最后空格再加上1);

3. 修复若干bugs。

当然,还有一些问题没有解决啦,比如DHCP超时不能捕获,导致报道正确链接,这些我在研究当中。

代码下载在wuxian_ver1.5代码

Linux的FC模拟器

突然怀念起FC游戏来了,于是兴起想玩玩。到网上收罗半天,发现众人推荐fceu这个模拟器,但make install以后,觉得该软件的默认按键极其麻烦(要用小键盘,用本的人好苦啊),同时这个软件不能运行时设置按键,只有改代码了。

linuxfans上搜到一个很老的帖子,修改方法记于此:找到src/drivers/pc/input.c288行,在这里直接修改按键成你想要的,重新编译之。再删除~/.fceultra目录,OK,一切正常了。

又可以玩超级玛丽2和坦克大战了,当年的最爱,呵呵。

发布一个本地测试的小工具

这几天做题我的错误率极高,经常都不得不去写数据生成器,然后找个正确的程序来对拍数据,所以全手工操作感觉很不方便,于是想有一个类似OJ一样的自动化测试工具。

想想OJ的实现原理无非也就是用diff来判断正误,所以利用这个原理我写了个工具,目前的测试方法只支持读入三个文件,分别是数据产生器的执行文件,待测试的执行文件和标准程序,然后指定数据组数,回车后就开始自动生成数据并测试,实例如下:

tester_tool 1 generator myprog stdprog 10

tester_tool 2 myprog input_folder output_folder

上面第一个1代表测试的模式,以上测试的模式编号为1,编号为2的模式即是OJ的实现方式,即预先产生好数据的输入和答案,然后用你的程序去产生输出并判断(输入数据和答案分别存储在input 和 output两个目录中)。该模式的使用模式需要注意的是输入文件和输出文件的名字必须一一对应,及test1.dat和test1.ans对应(后缀名没有要求),并且文件数量要一样多,否则会出错。模式3即是支持special judge且其余和1相同,4也是special judge且其余和2相同,但是都还未实现,:P。

测试的输出结果如下所示:

Test Case 8     Accepted

Test Case 9     Wrong Answer    (Test case transfer into tc9.txt)

目前只能判AC,WR和PE,如果错误的话相应的测试数据会被复制到对应文件中。

想急切试用的朋友不要慌,我必须要说下该工具的开发和运行环境:

我是在Mac OS X 10.4 下开发的,但是只用了标准Unix且为Posix标准的函数,因此在*nix下执行应该都不会有问题。
目前不支持Windows,因为我不会Windows开发,有兴趣的朋友可以帮忙移植下,谢谢。。

此程序目前还有很多功能没实现,并不是说我不开发了,今天放出来只是希望能尽早帮助无自动测试工具的朋友,我会尽快把剩下的功能完成,尽请关注我的blog,或者发现什么错误也请在此留言,我尽快修复,:P。

下载地址:autotester_001.zip

[zz] 可以在七种语言下编译的程序

这个世界真是什么事情都有…

[code:cpp]

(*O/*_/
Cu  #%* )pop mark/CuG 4 def/# 2 def%%%%@@P[TX—PP_SXPY!Ex(mx2ex(“SX!Ex4P)Ex=
CuG #%*                                                                  *+Ex=
CuG #%*——————————————————————*+Ex=
CuG #%*   POLYGLOT – a program in seven languages      15 February 1991  *+Ex=
CuG #%*                                                                  *+Ex=
CuG #%*   Written by Kevin Bungard, Peter Lisle, and Chris Tham          *+Ex=
CuG #%*                                                                  *+Ex=
CuG #%*   We have successfully run this program using the following:     *+Ex=
CuG #%*     ANSI COBOL:            MicroFocus COBOL85 (not COBOL74)      *+Ex=
CuG #%*     ISO  Pascal:           Turbo Pascal (DOS & Mac), Unix PC,    *+Ex=
CuG #%*                            AIX VS Pascal                         *+Ex=
CuG #%*     ANSI Fortran:          Unix f77, AIX VS Fortran              *+Ex=
CuG #%*     ANSI C (lint free):    Microsoft C, Unix CC, GCC, Turbo C++, *+Ex=
CuG #%*                            Think C (Mac)                         *+Ex=
CuG #%*     PostScript:            GoScript, HP/Adobe cartridge,         *+Ex=
CuG #%*                            Apple LaserWriter                     *+Ex=
CuG #%*     Shell script:          gnu bash, sh (SysV, BSD, MKS), ksh    *+Ex=
CuG #%*     8086 machine language: MS-DOS 2.00, 3.03, 4.01, 5.00 beta    *+Ex=
CuG #%*                            VPix & DOS Merge (under unix)         *+Ex=
CuG #%*                            SoftPC (on a Mac), MKS shell          *+Ex=
CuG #%*                                                                  *+Ex=
CuG #%*   Usage:                                                         *+Ex=
CuG #%*     1. Rename this file to polyglot.[cob|pas|f77|c|ps|sh|com]    *+Ex=
CuG #%*     2. Compile and/or run with appropriate compiler and          *+Ex=
CuG #%*        operating system                                          *+Ex=
CuG #%*                                                                  *+Ex=
CuG #%*   Notes:                                                         *+Ex=
CuG #%*     1. We have attempted to use only standard language features. *+Ex=
CuG #%*        Without the -traditional flag gcc will issue a warning.   *+Ex=
CuG #%*                                                                  *+Ex=
CuG #%*     2. This text is a comment block in all seven languages.      *+Ex=
CuG #%*                                                                  *+Ex=
CuG #%*     3. When run as a .COM file with MS-DOS it makes certain      *+Ex=
CuG #%*        (not unreasonable) assumptions about the contents of      *+Ex=
CuG #%*        the registers.                                            *+Ex=
CuG #%*                                                                  *+Ex=
CuG #%*     4. When transfering From Unix to DOS make sure that a LF     *+Ex=
CuG #%*        is correctly translated into a CR/LF.                     *+Ex=
CuG #%*                                                                  *+Ex=
CuG #%*   Please mail any comments, corrections or additions to          *+Ex=
CuG #%*   peril@extro.ucc.su.oz.au                                       *+Ex=
CuG #%*                                                                  *+Ex=
CuG #%*——————————————————————*QuZ=
CuG #%*                                                                  *+Ex=
CuG #%*!Mx)ExQX4ZPZ4SP5n#5X!)Ex+ExPQXH,B+ExP[-9Z-9Z)GA(W@’UTTER_XYZZY’CPK*+
CuG #(*                                                                  *(
C   # */);                                                              /*(
C   # *)  program        polyglot (output);                             (*+
C   #     identification division.
C   #     program-id.    polyglot.
C   #
C   #     data           division.
C   #     procedure      division.
C   #
C   # * ))cleartomark   /Bookman-Demi findfont 36 scalefont setfont     (
C   # *                                                                 (
C   #
C   # *                  hello polyglots$
C   #     main.
C   #         perform
C     * ) 2>_$$; echo   “hello polyglots”; rm _$$; exit
print
C             stop run.
-*,                ‘hello polyglots’
C
C         print.
C             display   “hello polyglots”.                              (
C     */  int i;                                                        /*
C     */  main () {                                                     /*
C     */      i=printf (“hello polyglotsn”); O= &i; return *O;         /*
C     *)                                                                (*
C     *)  begin                                                         (*
C     *)      writeln  (‘hello polyglots’);                             (*
C     *)                                                                (* )
C     * ) pop 60 360                                                    (
C     * ) pop moveto    (hello polyglots) show                          (
C     * ) pop showpage                                                  ((
C     *)
end                                                          .(* )
C)pop%     program       polyglot.                                      *){*/}

[/code]