C语言常见问题集原著:SteveSummit翻译:朱群英,孙云修订版0.
9.
4,2005年6月23日版权所有c2005目录目录i前言xvii1声明和初始化11.
1我如何决定使用那种整数类型11.
264位机上的64位类型是什么样的11.
3怎样定义和声明全局变量和函数最好21.
4extern在函数声明中是什么意思21.
5关键字auto到底有什么用途21.
6我似乎不能成功定义一个链表.
我试过typedefstruct{char*item;NODEPTRnext;}*NODEPTR;但是编译器报了错误信息.
难道在C语言中一个结构不能包含指向自己的指针吗31.
7怎样建立和理解非常复杂的声明例如定义一个包含N个指向返回指向字符的指针的函数的指针的数组31.
8函数只定义了一次,调用了一次,但编译器提示非法重定义了.
.
.
41.
9main()的正确定义是什么voidmain()正确吗41.
10对于没有初始化的变量的初始值可以作怎样的假定如果一个全局变量初始值为"零",它可否作为空指针或浮点零41.
11代码intf(){chara[]="Hello,world!
";}不能编译.
51.
12这样的初始化有什么问题char*p=malloc(10);编译器提示"非法初始式"云云.
51.
13以下的初始化有什么区别chara[]="stringliteral";char*p="stringliteral";当我向p[i]赋值的时候,我的程序崩溃了.
51.
14我总算弄清除函数指针的声明方法了,但怎样才能初始化呢.
.
52结构、联合和枚举72.
1声明structx1和typedefstructx2;有什么不同.
72.
2为什么structxxthestruct;不对72.
3一个结构可以包含指向自己的指针吗72.
4在C语言中实现抽象数据类型什么方法最好72.
5在C中是否有模拟继承等面向对象程序设计特性的好方法.
.
.
7i目录ii2.
6我遇到这样声明结构的代码:structname{intnamelen;charnamestr[1];};然后又使用一些内存分配技巧使namestr数组用起来好像有多个元素.
这样合法和可移植吗82.
7是否有自动比较结构的方法82.
8如何向接受结构参数的函数传入常数值82.
9怎样从/向数据文件读/写结构92.
10我的编译器在结构中留下了空洞,这导致空间浪费而且无法与外部数据文件进行"二进制"读写.
能否关掉填充,或者控制结构域的对齐方式92.
11为什么sizeof返回的值大于结构的期望值,是不是尾部有填充.
.
92.
12如何确定域在结构中的字节偏移92.
13怎样在运行时用名字访问结构中的域102.
14程序运行正确,但退出时却"coredump"了,怎么回事102.
15可以初始化一个联合吗102.
16枚举和一组预处理的#dene有什么不同102.
17有什么容易的显示枚举值符号的方法113表达式133.
1为什么这样的代码:a[i]=i++;不能工作133.
2使用我的编译器,下面的代码inti=7;printf("%d\n",i++*i++);返回49不管按什么顺序计算,难道不该打印出56吗133.
3对于代码inti=3;i=i++;不同编译器给出不同的结果,有的为3,有的为4,哪个是正确的143.
4这是个巧妙的表达式:a=b=a=b它不需要临时变量就可以交换a和b的值.
143.
5我可否用括号来强制执行我所需要的计算顺序143.
6可是&&和||运算符呢我看到过类似while((c=getchar())!
=EOF&&c!
='\n')的代码143.
7我怎样才能理解复杂表达式"序列点"是什么153.
8那么,对于a[i]=i++;我们不知道a[]的哪一个分量会被改写,但i的确会增加1,对吗153.
9++i和i++有什么区别153.
10如果我不使用表达式的值,我应该用++i或i++来自增一个变量吗153.
11为什么如下的代码inta=100,b=100;longintc=a*b;不能工作153.
12我需要根据条件把一个复杂的表达式赋值给两个变量中的一个.
可以用下边这样的代码吗((condition)a:b)=compli-catedexpression;16目录iii4指针174.
1我想声明一个指针并为它分配一些空间,但却不行.
这些代码有什么问题char*p;*p=malloc(10)174.
2*p++自增p还是p所指向的变量174.
3我有一个char*型指针正巧指向一些int型变量,我想跳过它们.
为什么如下的代码((int*)p)++;不行174.
4我有个函数,它应该接受并初始化一个指针voidf(int*ip){staticintdummy=5;ip=&dummy;}但是当我如下调用时:int*ip;f(ip);调用者的指针却没有任何变化.
184.
5我能否用void**指针作为参数,使函数按引用接受一般指针.
.
184.
6我有一个函数externintf(int*);它接受指向int型的指针.
我怎样用引用方式传入一个常数下面这样的调用f(&5);似乎不行.
.
184.
7C有"按引用传递"吗184.
8我看到了用指针调用函数的不同语法形式.
到底怎么回事.
.
.
194.
9我怎样把一个int变量转换为char*型我试了类型转换,但是不行.
195空(null)指针215.
1臭名昭著的空指针到底是什么215.
2怎样在程序里获得一个空指针215.
3用缩写的指针比较"if(p)"检查空指针是否可靠如果空指针的内部表达不是0会怎么样225.
4NULL是什么,它是怎么定义的235.
5在使用非全零作为空指针内部表达的机器上,NULL是如何定义的235.
6如果NULL定义成#deneNULL((char*)0)难道不就可以向函数传入不加转换的NULL了吗235.
7如果NULL和0作为空指针常数是等价的,那我到底该用哪一个呢245.
8但是如果NULL的值改变了,比如在使用非零内部空指针的机器上,难道用NULL(而不是0)不是更好吗245.
9用预定义宏#deneNullptr(type)(type*)0帮助创建正确类型的空指针.
245.
10这有点奇怪.
NULL可以确保是0,但空(null)指针却不一定.
.
245.
11为什么有那么多关于空指针的疑惑为什么这些问题如此经常地出现255.
12我很困惑.
我就是不能理解这些空指针一类的东西.
255.
13考虑到有关空指针的所有这些困惑,难道把要求它们内部表达都必须为0不是更简单吗265.
14说真的,真有机器用非零空指针吗,或者不同类型用不同的表达26目录iv5.
15运行时的"空指针赋值"错误是什么意思266数组和指针276.
1我在一个源文件中定义了chara[6],在另一个中声明了externchar*a.
为什么不行276.
2可是我听说chara[]和char*a是一样的.
276.
3那么,在C语言中"指针和数组等价"到底是什么意思286.
4那么为什么作为函数形参的数组和指针申明可以互换呢286.
5如果你不能给它赋值,那么数组如何能成为左值呢296.
6现实地讲,数组和指针地区别是什么296.
7有人跟我讲,数组不过是常指针.
296.
8我遇到一些"搞笑"的代码,包含5["abcdef"]这样的"表达式".
这为什么是合法的C表达式呢296.
9既然数组引用会蜕化为指针,如果arr是数组,那么arr和&arr又有什么区别呢306.
10我如何声明一个数组指针306.
11我如何在运行期设定数组的大小我怎样才能避免固定大小的数组306.
12我如何声明大小和传入的数组一样的局部数组306.
13我该如何动态分配多维数组316.
14有个灵巧的窍门:如果我这样写intrealarray[10];int*array=&realarray[-1];我就可以把"array"当作下标从1开始的数组.
.
.
326.
15当我向一个接受指针的指针的函数传入二维数组的时候,编译器报错了.
326.
16我怎样编写接受编译时宽度未知的二维数组的函数326.
17我怎样在函数参数传递时混用静态和动态多维数组336.
18当数组是函数的参数时,为什么sizeof不能正确报告数组的大小347内存分配357.
1为什么这段代码不行char*answer;printf("Typesomething:\n");gets(answer);printf("Youtyped\"%s\"\n",answer)357.
2我的strcat()不行.
我试了char*s1="Hello,";char*s2="world!
";char*s3=strcat(s1,s2);但是我得到了奇怪的结果.
357.
3但是strcat的手册页说它接受两个char*型参数.
我怎么知道(空间)分配的事情呢367.
4我刚才试了这样的代码char*p;strcpy(p,"abc");而它运行正常怎么回事为什么它没有崩溃367.
5一个指针变量分配多少内存367.
6我有个函数,本该返回一个字符串,但当它返回调用者的时候,返回串却是垃圾信息.
36目录v7.
7那么返回字符串或其它集合的争取方法是什么呢377.
8为什么在调用malloc()时,我得到"警告:整数赋向指针需要类型转换"377.
9为什么有些代码小心地把malloc返回的值转换为分配的指针类型.
377.
10在调用malloc()的时候,错误"不能把void*转换为int*"是什么意思377.
11我见到了这样的代码char*p=malloc(strlen(s)+1);strcpy(p,s);难道不应该是malloc((strlen(s)+1)*sizeof(char)377.
12我如何动态分配数组387.
13我听说有的操作系统程序使用的时候才真正分配malloc申请的内存.
这合法吗387.
14我用一行这样的代码分配一个巨大的数组,用于数字运算:double*array=malloc(300*300*sizeof(double));malloc()并没有返回null,但是程序运行得有些奇怪,好像改写了某些内存,或者malloc()并没有分配我申请的那么多内存,云云.
387.
15我的PC有8兆内存.
为什么我只能分配640K左右的内存.
.
387.
16我的程序总是崩溃,显然在malloc内部的某个地方.
但是我看不出哪里有问题.
是malloc()有bug吗387.
17动态分配的内存一旦释放之后你就不能再使用,是吧387.
18为什么在调用free()之后指针没有变空使用(赋值,比较)释放之后的指针有多么不安全397.
19当我malloc()为一个函数的局部指针分配内存时,我还需要用free()明确的释放吗397.
20我在分配一些结构,它们包含指向其它动态分配的对象的指针.
我在释放结构的时候,还需要释放每一个下级指针吗397.
21我必须在程序退出之前释放分配的所有内存吗407.
22我有个程序分配了大量的内存,然后又释放了.
但是从操作系统看,内存的占用率却并没有回去.
407.
23free()怎么知道有多少字节需要释放407.
24那么我能否查询malloc包,可分配的最大块是多大407.
25向realloc()的第一个参数传入空指针合法吗你为什么要这样做407.
26calloc()和malloc()有什么区别利用calloc的零填充功能安全吗free()可以释放calloc()分配的内存吗,还是需要一个cfree()407.
27alloca()是什么为什么不提倡使用它418字符和字符串438.
1为什么strcat(string,'!
');不行438.
2我在检查一个字符串是否跟某个值匹配.
为什么这样不行char*string;.
.
.
if(string=="value"){/*stringmatches"value"*/43目录vi8.
3如果我可以写chara[]="Hello,world!
";为什么我不能写chara[14];a="Hello,world!
438.
4我怎么得到对应字符的数字(字符集)值,或者相反448.
5我认为我的编译器有问题:我注意到sizeof('a')是2而不是1(即,不是sizeof(char)449布尔表达式和变量459.
1C语言中布尔值的候选类型是什么为什么它不是一个标准类型我应该用#dene或enum定义true和false值吗459.
2因为在C语言中所有的非零值都被看作"真",是不是把TRUE定义为1很危险如果某个内置的函数或关系操作符"返回"不是1的其它值怎么办459.
3当p是指针时,if(p)是合法的表达式吗4610C预处理器4710.
1这些机巧的预处理宏:#denebegin{#deneend}你觉得怎么样4710.
2怎么写一个一般用途的宏交换两个值4710.
3书写多语句宏的最好方法是什么4710.
4我第一次把一个程序分成多个源文件,我不知道该把什么放到.
c文件,把什么放到.
h文件.
(".
h"到底是什么意思4810.
5一个头文件可以包含另一头文件吗4810.
6#include和#include""有什么区别4810.
7完整的头文件搜索规则是怎样的4910.
8我在文件的第一个声明就遇到奇怪的语法错误,但是看上去没什么问题.
4910.
9我包含了我使用的库函数的正确头文件,可是连接器还是说它没有定义.
4910.
10我在编译一个程序,看起来我好像缺少需要的一个或多个头文件.
谁能发给我一份4910.
11我怎样构造比较字符串的#if预处理表达式4910.
12sizeof操作符可以用于#if预编译指令中吗5010.
13我可以在#include行里使用#ifdef来定义两个不同的东西吗.
5010.
14对typdef的类型定义有没有类似#ifdef的东西5010.
15我如何用#if表达式来判断机器是高字节在前还是低字节在前.
5010.
16我得到了一些代码,里边有太多的#ifdef.
我不想使用预处理器把所有的#include和#ifdef都扩展开,有什么办法只保留一种条件的代码呢5010.
17如何列出所有的预定义标识符50目录vii10.
18我有些旧代码,试图用这样的宏来构造标识符#denePaste(a,b)a/**/b但是现在不行了.
5110.
19为什么宏#deneTRACE(n)printf("TRACE:%d\n",n)报出警告"用字符串常量代替宏"它似乎应该把TRACE(count);扩展为printf("TRACE:%d\count",count)5110.
20使用#操作符时,我在字符串常量内使用宏参数有问题.
5110.
21我想用预处理做某件事情,但却不知道如何下手.
5110.
22怎样写参数个数可变的宏5111ANSI/ISO标准C5311.
1什么是"ANSIC标准"5311.
2我如何得到一份标准的副本5311.
3我在哪里可以找到标准的更新5411.
4很多ANSI编译器在遇到以下代码时都会警告类型不匹配.
externintfunc(oat);intfunc(x)oatx;5411.
5能否混用旧式的和新型的函数语法5511.
6为什么声明externintf(structx*p);报出了一个奇怪的警告信息"结构x在参数列表中声明"5511.
7我不明白为什么我不能象这样在初始化和数组维度中使用常量:constintn=5;inta[n]5511.
8既然不能修改字符串常量,为什么不把它们定义为字符常量的数组5511.
9"constchar*p"和"char*constp"有何区别5611.
10为什么我不能向接受constchar**的函数传入char5611.
11怎样正确声明main(5611.
12我能否把main()定义为void,以避免扰人的"main无返回值"警告5611.
13可main()的第三个参数envp是怎么回事5711.
14我觉得把main()声明为void不会失败,因为我调用了exit()而不是return,况且我的操作系统也忽略了程序的退出/返回状态.
.
.
5711.
15那么到底会出什么问题真的有什么系统不支持voidmain()吗5711.
16我一直用的那本书《熟练傻瓜C语言》总是使用voidmain().
.
.
5711.
17从main()中,exit(status)和返回同样的status真的等价吗.
.
.
5711.
18我试图用ANSI"字符串化"预处理操作符#向信息中插入符号常量的值,但它字符串化的总是宏的名字而不是它的值.
5811.
19警告信息"warning:macroreplacementwithinastringliteral"是什么意思5811.
20在我用#ifdef去掉的代码里出现了奇怪的语法错误.
5811.
21#pragma是什么,有什么用5911.
22"#pragmaonce"是什么意思我在一些头文件中看到了它.
.
.
59目录viii11.
23a[3]="abc";合法吗它是什么意思5911.
24为什么我不能对void*指针进行运算5911.
25memcpy()和memmove()有什么区别5911.
26malloc(0)有什么用返回一个控指针还是指向0字节的指针.
5911.
27为什么ANSI标准规定了外部标示符的长度和大小写限制.
.
.
6011.
28我的编译对最简单的测试程序报出了一大堆的语法错误.
6011.
29为什么有些ASNI/ISO标准库函数未定义我明明使用的就是ANSI编译器.
6011.
30谁有把旧的C程序转化为ANSIC或相反的工具,或者自动生成原型的工具6011.
31为什么声称兼容ANSI的FrobozzMagicC编译器不能编译这些代码我知道这些代码是ANSI的,因为gcc可以编译.
6011.
32人们好像有些在意实现定义(implementation-den-ed)、未明确(unspecied)和无定义(undened)行为的区别.
它们的区别到底在哪里6111.
33一个程序的"合法","有效"或"符合"到底是什么意思6111.
34我很吃惊,ANSI标准竟然有那么多没有定义的东西.
标准的唯一任务不就是让这些东西标准化吗6111.
35有人说i=i++的行为是未定义的,但是我刚在一个兼容ANSI的编译器上测试,得到了我希望的结果.
6212标准输入输出库6312.
1这样的代码有什么问题charc;while((c=getchar())!
=EOF).
.
.
6312.
2我有个读取直到EOF的简单程序,但是我如何才能在键盘上输入那个"EOF"呢6312.
3为什么这些代码while(!
feof(infp)){fgets(buf,MAXLINE,infp);fputs(buf,outfp);}把最后一行复制了两遍6312.
4我的程序的屏幕提示和中间输出有时显示在屏幕上,尤其是当我用管道向另一个程序输出的时候.
6312.
5我怎样不等待回车键一次输入一个字符6412.
6我如何在printf的格式串中输出一个'%'我试过\%,但是不行.
6412.
7有人告诉我在printf中使用%lf不正确.
那么,如果scanf()需要%lf,怎么可以用在printf()中用%f输出双精度数呢6412.
8对于sizet那样的类型定义,当我不知道它到底是long还是其它类型的时候,我应该使用什么样的printf格式呢6412.
9我如何用printf实现可变的域宽度就是说,我想在运行时确定宽度而不是使用%8d6412.
10如何输出在千位上用逗号隔开的数字金额数字呢6512.
11为什么scanf("%d",i)调用不行6512.
12为什么chars[30];scanf("%s",s);不用&也可以65目录ix12.
13为什么这些代码doubled;scanf("%f",&d);不行6512.
14怎样在scanf()格式串中指定可变的宽度6512.
15当我用"%d\n"调用scanf从键盘读取数字的时候,好像要多输入一行函数才返回.
6512.
16我用scanf%d读取一个数字,然后再用gets()读取字符串,但是编译器好像跳过了gets()调用!
6612.
17我发现如果坚持检查返回值以确保用户输入的是我期待的数值,则scanf()的使用会安全很多,但有的时候好像会陷入无限循环.
.
6612.
18为什么大家都说不要使用scanf()那我该用什么来代替呢.
.
.
6612.
19我怎样才知道对于任意的sprintf调用需要多大的目标缓冲区怎样才能避免sprintf()目标缓冲区溢出6612.
20为什么大家都说不要使用gets(6712.
21为什么调用printf()之后errno内有ENOTTY6712.
22fgetops/fsetops和ftell/fseek之间有什么区别fgetops()和fse-tops()到底有什么用处6812.
23如何清除多余的输入,以防止在下一个提示符下读入ush(stdin)可以吗6812.
24既然ush()不能,那么怎样才能清除输入呢6812.
25对某些路径文件名调用fopen()总是失败.
6812.
26我想用"r+"打开一个文件,读出一个字符串,修改之后再写入,从而就地更新一个文件.
可是这样不行.
6912.
27怎样在程序里把stdin或stdout重定向到文件6912.
28一旦使用freopen()之后,怎样才能恢复原来的stdout(或stdin)6912.
29怎样同时向两个地方输出,如同时输出到屏幕和文件6912.
30怎样正确的读取二进制文件我有时看到0x0a和0x0d混淆了,而且如果数据中包含0x1a的话,我好像会提前遇到EOF.
7013库函数7113.
1怎样把数字转为字符串(与atoi相反)有itoa()函数吗7113.
2为什么strncpy()不能总在目标串放上终止符'\0'7113.
3为什么有些版本的toupper()对大写字符会有奇怪的反应为什么有的代码在调用toupper()前先调用tolower(7113.
4怎样把字符串分隔成用空白作间隔符的段怎样实现类似传递给main()的argc和argv7213.
5我需要一些处理正则表达式或通配符匹配的代码.
7213.
6我想用strcmp()作为比较函数,调用qsort()对一个字符串数组排序,但是不行.
7213.
7我想用qsort()对一个结构数组排序.
我的比较函数接受结构指针,但是编译器认为这个函数对于qsort()是错误类型.
我要怎样转换这个函数指针才能避免这样的警告73目录x13.
8怎样对一个链表排序7313.
9怎样对多于内存的数据排序7313.
10怎样在C程序中取得当前日期或时间7313.
11我知道库函数localtime()可以把timet转换成结构structtm,而ctime()可以把timet转换成为可打印的字符串.
怎样才能进行反向操作,把structtm或一个字符串转换成timet7413.
12怎样在日期上加N天怎样取得两个日期的时间间隔7413.
13我需要一个随机数生成器.
7513.
14怎样获得在一定范围内的随机数7513.
15每次执行程序,rand()都返回相同顺序的数字.
7513.
16我需要随机的真/假值,所以我用直接用rand()%2,可是我得到交替的0,1,0,1,07613.
17怎样产生标准分布或高斯分布的随机数7613.
18我不断得到库函数未定义错误,但是我已经#inlude了所有用到的头文件了.
7713.
19虽然我在连接时明确地指定了正确的函数库,我还是得到库函数未定义错误.
7713.
20连接器说end未定义代表什么意思7713.
21我的编译器提示printf未定义!
这怎么可能7714浮点运算7914.
1一个oat变量赋值为3.
1时,为什么printf输出的值为3.
09999997914.
2执行一些开方根运算,可是得到一些疯狂的数字.
7914.
3做一些简单的三角函数运算,也引用了#include,可是一直得到编译错误"undened:sin"(函数sin未定义)7914.
4浮点计算程序表现奇怪,在不同的机器上给出不同的结果.
7914.
5有什么好的方法来验对浮点数在"足够接近"情况下的等值.
.
.
8014.
6怎样取整数8014.
7为什么C不提供乘幂的运算符8014.
8为什么我机器上的没有预定义常数MPI8014.
9怎样测试IEEENaN以及其它特殊值8114.
10在C中如何很好的实现复数8114.
11我要寻找一些实现以下功能的程序源代码:快速傅立叶变换(FFT)、矩阵算术(乘法、倒置等函数)、复数算术.
8114.
12TurboC的程序崩溃,显示错误为"oatingpointformatsnotlinked"(浮点格式未连接)8115可变参数8315.
1为什么调用printf()前,必须要用#include83目录xi15.
2为什么%f可以在printf()参数中,同时表示oat和double他们难道不是不同类型吗8315.
3为什么当n为longint,printf("%d",n);编译时没有匹配警告我以为ANSI函数原型可以防止这样的类型不匹配.
8315.
4怎样写一个有可变参数的函数8315.
5怎样写类似printf()的函数,再把参数转传给printf()去完成大部分工作8515.
6怎样写类似scanf()的函数,再把参数转传给scanf()去完成大部分工作8515.
7怎样知道实际上有多少个参数传入函数8515.
8为什么编译器不让我定义一个没有固定参数项的可变参数函数.
8615.
9我有个接受oat的可变参函数,为什么vaarg(argp,oat)不工作8615.
10vaarg()不能得到类型为函数指针的参数.
8615.
11怎样实现一个可变参数函数,它把参数再传给另一个可变参数函数8615.
12怎样调用一个参数在执行是才建立的函数8716奇怪的问题8916.
1遇到不可理解的不合理语法错误,似乎大段的程序没有编译.
.
.
8916.
2为什么过程调用不工作编译器似乎直接跳过去了.
8916.
3程序在执行用之前就崩溃了,用调试器单步跟进,在main()之前就死了.
8916.
4程序执行正确,但退出时崩溃在main()最后一个语句之后.
为什么会这样8916.
5程序在一台机器上执行完美,但在另一台上却得到怪异的结果.
更奇怪的是,增加或去除调试的打印语句,就改变了症状…9016.
6为什么代码:char*p="hello,worl!
";p[0]='H';会崩溃.
.
.
9016.
7"Segmentationviolation","Buserror"和"Generalprotectionfault"意味着什么9117风格9317.
1什么是C最好的代码布局风格9317.
2用if(!
strcmp(s1,s2))比较两个字符串等值,是否是个好风格.
.
.
9317.
3为什么有的人用if(0==x)而不是if(x==0)9317.
4原型说明externintfunc((int,int));中,那些多出来的括号和下划线代表了什么9417.
5为什么有些代码在每次调用printf()前,加了类型转换(void).
.
9417.
6什么是"匈牙利标志法"(HungarianNotation)是否值得用.
.
9417.
7哪里可以找到"印第安山风格指南"(IndianHillStyleGuide)及其它编码标准94目录xii17.
8有些人说goto是邪恶的,我应该永不用它.
那是否太极端了.
.
9518工具和资源9718.
1常用工具列表.
9718.
2怎样抓捕棘手的malloc问题9818.
3有什么免费或便宜的编译器可以使用9818.
4刚刚输入完一个程序,但它表现的很奇怪.
你可以发现有什么错误的地方吗9818.
5哪里可以找到兼容ANSI的lint9918.
6难道ANSI函数原型说明没有使lint过时吗9918.
7网上有哪些C的教程或其它资源9918.
8哪里可以找到好的源代码实例,以供研究和学习10018.
9有什么好的学习C的书有哪些高级的书和参考10018.
10哪里可以找到标准C函数库的源代码10118.
11是否有一个在线的C参考指南10118.
12哪里可以得到ANSI/ISOC标准10118.
13我需要分析和评估表达式的代码.
10118.
14哪里可以找到C的BNF或YACC语法10118.
15谁有C编译器的测试套件10218.
16哪里有一些有用的源代码片段和例子的收集10218.
17我需要执行多精度算术的代码.
10218.
18在哪里和怎样取得这些可自由发布的程序10219系统依赖10519.
1怎样从键盘直接读入字符而不用等RETURN键怎样防止字符输入时的回显10519.
2怎样知道有未读的字符,如果有,有多少如果没有字符,怎样使读入不阻断10619.
3怎样显示一个百分比或"转动的短棒"的进展表示器10619.
4怎样清屏怎样输出彩色文本怎样移动光标到指定位置.
.
.
10619.
5怎样读入方向键,功能键10719.
6怎样读入鼠标输入10719.
7怎样做串口("comm")的输入输出10719.
8怎样直接输出到打印机10719.
9怎样发送控制终端或其它设备的逃逸指令序列10819.
10怎样直接访问输入输出板10819.
11怎样做图形10819.
12怎样显示GIF和JPEG图象10819.
13怎样检验一个文件是否存在10819.
14怎样在读入文件前,知道文件大小109目录xiii19.
15怎样得到文件的修改日期和时间10919.
16怎样缩短一个文件而不用清除或重写10919.
17怎样在文件中插入或删除一行(或记录)10919.
18怎样从一个打开的流或文件描述符得到文件名11019.
19怎样删除一个文件11019.
20怎样复制一个文件11019.
21为什么用了详尽的路径还不能打开文件fopen("c:\newdir\le.
dat","r")返回错误.
11019.
22fopen()不让我打开文件:"$HOME/.
prole"和"/.
myrcle".
.
11119.
23怎样制止MS-DOS下令人担忧的"Abort,Retry,Ignore"信息11119.
24遇到"Toomanyopenles(打开文件太多)"的错误,怎样增加同时打开文件的允许数目11119.
25怎样在C中读入目录11119.
26怎样找出系统还有多少内存可用11119.
27怎样分配大于64K的数组或结构11119.
28错误信息"DGROUPdataallocationexceeds64K(DGROUP数据分配内存超过64K)"说明什么我应该怎么做我以为使用了大内存模型,那我就可以使用多于64K的数据!
11219.
29怎样访问位于某的特定地址的内存(内存映射的设备或图显内存)11219.
30怎样在一个C程序中调用另一个程序(独立可执行的程序,或系统命令)11219.
31怎样调用另一个程序或命令,同时收集它的输出11319.
32怎样才能发现程序自己的执行文件的全路径11319.
33怎样找出和执行文件在同一目录的配置文件11319.
34一个进程如何改变它的调用者的环境变量11319.
35怎样读入一个对象文件并跳跃到其中的地址11419.
36怎样实现精度小于秒的延时或记录用户回应的时间11419.
37怎样抓获或忽略像control-C这样的键盘中断11419.
38怎样很好地处理浮点异常11519.
39怎样使用socket网络化写客户/服务器程序11519.
40怎样调用BIOS函数写ISR创建TSR11519.
41编译程序,编译器出示"unionREGS"未定义错误信息,连接器出示"int86()"的未定义错误信息.
11519.
42什么是"near"和"far"指针11619.
43我不能使用这些非标准、依赖系统的函数,程序需要兼容ANSI!
.
11620杂项11720.
1怎样从一个函数返回多个值11720.
2怎样访问命令行参数117目录xiv20.
3怎样写数据文件,使之可以在不同字大小、字节顺序或浮点格式的机器上读入11720.
4怎样调用一个由char*指针指向函数名的函数11720.
5怎样实现比特数组或集合11820.
6怎样判断机器的字节顺序是高字节在前还是低字节在前11820.
7怎样掉换字节11820.
8怎样转换整数到二进制或十六进制11920.
9我可以使用二进制常数吗有printf()的二进制的格式符吗.
.
11920.
10什么是计算整数中比特为1的个数的最有效的方法11920.
11什么是提高程序效率的最好方法11920.
12指针真得比数组快吗函数调用会拖慢程序多少++i比i=i+1快吗12020.
13人们说编译器优化的很好,我们不在需要为速度而写汇编了,但我的编译器连用移位代替i/=2都做不到.
12020.
14怎样不用临时变量而交换两个值12020.
15是否有根据字符串做切换的方法12120.
16是否有使用非常量case标志的方法(例如范围或任意的表达式)12120.
17return语句外层的括号是否真的可选择12120.
18为什么C注释不能嵌套怎样注释掉含有注释的代码引用字符串内的注释是否合法12120.
19C是个伟大的语言还是别的哪个其它语言可以写象a+++++b这样的代码12220.
20为什么C没有嵌套函数12220.
21assert()是什么怎样用它12220.
22怎样从C中调用FORTRAN(C++,BASIC,Pascal,Ada,LISP)的函数反之亦然12220.
23有什么程序可以做从Pascal或Fortran(或LISP,Ada,awk,"老"C)到C的转换12320.
24C++是C的超集吗可以用C++编译器来编译C代码吗.
.
12320.
25需要用到"近似"的strcmp,比较两个字符串的近似度,并不需要完全一样.
12320.
26什么是散列法12420.
27由一个日期,怎样知道是星期几12420.
28(year%4==0)是否足够判断润年2000年是闰年吗12420.
29一个难题:怎样写一个输出自己源代码的程序12420.
30什么是"达夫设备"(Du'sDevice)12520.
31下届国际C混乱代码竞赛(IOCCC)什么时候进行哪里可以找到当前和以前的获胜代码12520.
32[K&R1]提到的关健字entry是什么126目录xv20.
33C的名字从何而来12620.
34"char"如何发音12620.
35"lvalue"和"rvalue"代表什么意思12620.
36哪里可以取得本FAQ(英文版)的额外副本12621感谢129文献131目录xvi前言本文从英文C–FAQ(2004年7月3日修订版)翻译而来.
本文的中文版权为朱群英和孙云所有.
本文的内容可以自由用于个人目的,但是不可以未经许可出版发行.
英文版权为SteveSummit所有,详情见下面的英文版权说明.
TheEnglishversionofthisFAQlistisCopyright1990-2004bySteveSummit.
Contentfromthebook《CProgrammingFAQs:FrequentlyAskedQuestions》ismadeavailableherebypermissionoftheauthorandthepublisherasaservicetothecommunity.
Itisintendedtocomplementtheuseofthepublishedtextandisprotectedbyinternationalcopyrightlaws.
Theon-linecontentmaybeaccessedfreelyforpersonalusebutmaynotberepublishedwithoutpermission.
最新的HTML中译版本可以在http://c-faq-chn.
sourceforge.
net/取得.
另外在同一地址还提供PDF版本的下载.
在http://sourceforge.
net/projects/c-faq-chn可以得到本文的LATEX源文件.
有关英文原文的问题,请咨询SteveSummit(scs@eskimo.
com).
有关中文译稿的问题,请联系孙云(sunyun.
s@gmail.
com,1–12章)和朱群英(zhu.
qunying@gmail.
com,13–20章、LATEX文件编辑).
xvii第1章声明和初始化1.
1我如何决定使用那种整数类型如果需要大数值(大于32,767或小于32,767),使用long型.
否则,如果空间很重要(如有大数组或很多结构),使用short型.
除此之外,就使用int型.
如果严格定义的溢出特征很重要而负值无关紧要,或者你希望在操作二进制位和字节时避免符号扩展的问题,请使用对应的无符号类型.
但是,要注意在表达式中混用有符号和无符号值的情况.
尽管字符类型(尤其是无符号字符型)可以当成"小"整型使用,但由于不可预知的符号扩展和代码增大有时这样做可能得不偿失.
使用无符号字符型有所帮助;类似的问题参见问题12.
1.
在选择浮点型和双精度浮点型时也有类似的权衡.
但如果一个变量的指针必须为特定的类型时,以上规则不再适用.
如果因为某种原因你需要声明一个有严格大小的变量,确保象C99的那样用某种适当的typedef封装这种选择.
通常,这样做唯一的好原因是试图符合某种外部强加的存储方案,请参见问题20.
3.
如果你需要操作超过C的内置类型支持的超大变量,请参见问题18.
17.
参考资料:[K&R1,Sec.
2.
2p.
34];[K&R2,Sec.
2.
2p.
36,Sec.
A4.
2pp.
195-6,Sec.
B11p.
257];[ISO,Sec.
5.
2.
4.
2.
1,Sec.
6.
1.
2.
5];[H&S,Secs.
5.
1,5.
2pp.
110-114].
1.
264位机上的64位类型是什么样的C99标准定义了longlong类型,其长度可以保证至少64位,这种类型在某些编译器上实现已经颇有时日了.
其它的编译器则实现了类似longlong的扩展.
另一方面,也可以实现16位的短整型、32位的整型和64位的长整型,有些编译器正是这样做的.
参见问题18.
17.
参考资料:[C9X,Sec.
5.
2.
4.
2.
1,Sec.
6.
1.
2.
5]1第1章声明和初始化21.
3怎样定义和声明全局变量和函数最好首先,尽管一个全局变量或函数可以(在多个编译单元中)有多处"声明",但是"定义"却只能允许出现一次.
定义是分配空间并赋初值(如果有)的声明.
最好的安排是在某个相关的.
c文件中定义,然后在头文件(.
h)中进行外部声明,在需要使用的时候,只要包含对应的头文件即可.
定义变量的.
c文件也应该包含该头文件,以便编译器检查定义和声明的一致性.
这条规则提供了高度的可移植性:它和ANSIC标准一致,同时也兼容大多数ANSI前的编译器和连接器.
Unix编译器和连接器通常使用"通用模式"允许多重定义,只要保证最多对一处进行初始化就可以了;ANSIC标准称这种行为为"公共扩展",没有语带双关的意思.
可以使用预处理技巧来使类似DEFINE(int,i);的语句在一个头文件中只出现一次,然后根据某个宏的设定在需要的时候转化成定义或声明.
但不清楚这样的麻烦是否值得.
如果希望让编译器检查声明的一致性,一定要把全局声明放到头文件中.
特别是,永远不要把外部函数的原型放到.
c文件中:通常它与定义的一致性不能得到检查,而矛盾的原型比不用还糟糕.
参见问题10.
4和18.
6.
参考资料:[K&R1,Sec.
4.
5pp.
76-7];[K&R2,Sec.
4.
4pp.
80-1];[ISO,Sec.
6.
1.
2.
2,Sec.
6.
7,Sec.
6.
7.
2,Sec.
G.
5.
11];[Rationale,Sec.
3.
1.
2.
2];[H&S,Sec.
4.
8pp.
101-104,Sec.
9.
2.
3p.
267];[CT&P,Sec.
4.
2pp.
54-56].
1.
4extern在函数声明中是什么意思它可以用作一种格式上的提示表明函数的定义可能在另一个源文件中,但在externintf();和intf();之间并没有实质的区别.
参考资料:[ISO,Sec.
6.
1.
2.
2,Sec.
6.
5.
1];[Rationale,Sec.
3.
1.
2.
2];[H&S,Secs.
4.
3,4.
3.
1pp.
75-6].
1.
5关键字auto到底有什么用途毫无用途;它已经过时.
参见问题20.
32.
参考资料:[K&R1,Sec.
A8.
1p.
193];[ISO,Sec.
6.
1.
2.
4,Sec.
6.
5.
1;];[H&S,Sec.
4.
3p.
75,Sec.
4.
3.
1p.
76].
第1章声明和初始化31.
6我似乎不能成功定义一个链表.
我试过typedefstruct{char*item;NODEPTRnext;}*NODEPTR;但是编译器报了错误信息.
难道在C语言中一个结构不能包含指向自己的指针吗C语言中的结构当然可以包含指向自己的指针;[K&R2,第6.
5节]的讨论和例子表明了这点.
NODEPTR例子的问题是在声明next域的时候typedef还没有定义.
为了解决这个问题,首先赋予这个结构一个标签("structnode").
然后,声明"next"域为"structnode*",或者分开typedef定义和结构定义,或者两者都采纳.
以下是一个修改后的版本:structnode{char*item;structnode*next;};typedefstructnode*NODEPTR;至少还有三种同样正确的方法解决这个问题.
在用typedef定义互相引用的两个结构时也会产生类似的问题,可以用同样的方法解决.
参见问题2.
1.
参考资料:[K&R1,Sec.
6.
5p.
101];[K&R2,Sec.
6.
5p.
139];[ISO,Sec.
6.
5.
2,Sec.
6.
5.
2.
3];[H&S,Sec.
5.
6.
1pp.
132-3].
1.
7怎样建立和理解非常复杂的声明例如定义一个包含N个指向返回指向字符的指针的函数的指针的数组这个问题至少有以下3种答案:1.
char*(*(*a[N])())();2.
用typedef逐步完成声明:typedefchar*pc;/*字符指针*/typedefpcfpc();/*返回字符指针的函数*/typedeffpc*pfpc;/*上面函数的指针*/typedefpfpcfpfpc();/*返回函数指针的函数*/typedeffpfpc*pfpfpc;/*上面函数的指针*/pfpfpca[N];/*上面指针的数组*/3.
使用cdecl程序,它可以把英文翻译成C或者把C翻译成英文:cdecl>declareaasarrayofpointertofunctionreturningpointertofunctionreturningpointertocharchar*(*(*a[])())()通过类型转换,cdecl也可以用于解释复杂的声明,指出参数应该进入哪一对括号(如同在上述的复杂函数定义中).
参见问题18.
1.
第1章声明和初始化4一本好的C语言书都会解释如何"从内到外"解释和理解这样复杂的C语言声明("模拟声明使用").
上文的例子中的函数指针声明还没有包括参数类型信息.
如果参数有复杂类型,声明就会变得真正的混乱了.
现代的cdecl版本可以提供帮助.
参考资料:[K&R2,Sec.
5.
12p.
122];[ISO,Sec.
6.
5(esp.
Sec.
6.
5.
4)];[H&S,Sec.
4.
5pp.
85-92,Sec.
5.
10.
1pp.
149-50].
1.
8函数只定义了一次,调用了一次,但编译器提示非法重定义了.
在范围内没有声明就调用(可能是第一次调用在函数的定义之前)的函数被认为返回整型(int)(且没有任何参数类型信息),如果函数在后边声明或定义成其它类型就会导致矛盾.
所有函数(非整型函数一定要)必须在调用之前声明.
另一个可能的原因是该函数与某个头文件中声明的另一个函数同名.
参见问题11.
4和15.
1参考资料:[K&R1,Sec.
4.
2p.
70];[K&R2,Sec.
4.
2p.
72];[ISO,Sec.
6.
3.
2.
2];[H&S,Sec.
4.
7p.
101].
1.
9main()的正确定义是什么voidmain()正确吗参见问题11.
11到11.
16.
(这样的定义不正确).
1.
10对于没有初始化的变量的初始值可以作怎样的假定如果一个全局变量初始值为"零",它可否作为空指针或浮点零具有"静态"生存期的未初始化变量(即,在函数外声明的变量和有静态存储类型的变量)可以确保初始值为零,就像程序员键入了"=0"一样.
因此,这些变量如果是指针会被初始化为正确的空指针,如果是浮点数会被初始化为0.
0(或正确的类型,参见第5章).
具有"自动"生存期的变量(即,没有静态存储类型的局部变量)如果没有显示地初始化,则包含的是垃圾内容.
对垃圾内容不能作任何有用的假设.
这些规则也适用于数组和结构(称为"聚合体");对于初始化来说,数组和结构都被认为是"变量".
用malloc()和realloc()动态分配的内存也可能包含垃圾数据,因此必须由调用者正确地初始化.
用calloc()获得的内存为全零,但这对指针和浮点值不一定有用(参见问题7.
26和第5章).
参考资料:[K&R1,Sec.
4.
9pp.
82-4];[K&R2,Sec.
4.
9pp.
85-86];[ISO,Sec.
6.
5.
7,Sec.
7.
10.
3.
1,Sec.
7.
10.
5.
3];[H&S,Sec.
4.
2.
8pp.
72-3,Sec.
4.
6pp.
92-3,Sec.
4.
6.
2pp.
94-5,Sec.
4.
6.
3p.
96,Sec.
16.
1p.
386.
].
第1章声明和初始化51.
11代码intf(){chara[]="Hello,world!
";}不能编译.
可能你使用的是ANSI之前的编译器,还不支持"自动聚集"(automaticaggre-gates,即非静态局部数组、结构和联合)的初始化.
参见问题11.
28.
1.
12这样的初始化有什么问题char*p=malloc(10);编译器提示"非法初始式"云云.
这个声明是静态或非局部变量吗函数调用只能出现在自动变量(即局部非静态变量)的初始式中.
1.
13以下的初始化有什么区别chara[]="stringliteral";char*p="stringliteral";当我向p[i]赋值的时候,我的程序崩溃了.
字符串常量有两种稍有区别的用法.
用作数组初始值(如同在chara[]的声明中),它指明该数组中字符的初始值.
其它情况下,它会转化为一个无名的静态字符数组,可能会存储在只读内存中,这就是造成它不一定能被修改.
在表达式环境中,数组通常被立即转化为一个指针(参见第6章),因此第二个声明把p初始化成指向无名数组的第一个元素.
为了编译旧代码,有的编译器有一个控制字符串是否可写的开关.
参见问题1.
11、6.
1、6.
2和6.
6.
参考资料:[K&R2,Sec.
5.
5p.
104];[ISO,Sec.
6.
1.
4,Sec.
6.
5.
7];[Rationale,Sec.
3.
1.
4];[H&S,Sec.
2.
7.
4pp.
31-2].
1.
14我总算弄清除函数指针的声明方法了,但怎样才能初始化呢用下面这样的代码externintfunc();int(*fp)()=func;当一个函数名出现在这样的表达式中时,它就会"蜕变"成一个指针(即,隐式地取出了它的地址),这有点类似数组名的行为.
通常函数的显示声明需要事先知道(也许在一个头文件中).
因为此处并没有隐式的外部函数声明(初始式中函数名并非一个函数调用的一部分).
参见问题1.
8和4.
8.
第1章声明和初始化6第2章结构、联合和枚举2.
1声明structx1和typedefstructx2;有什么不同第一种形式声明了一个"结构标签";第二种声明了一个"类型定义".
主要的区别是在后文中你需要用"structx1"引用第一种,而用"x2"引用第二种.
也就是说,第二种声明更像一种抽象类新—–用户不必知道它是一个结构,而在声明它的实例时也不需要使用struct关键字.
2.
2为什么structxxthestruct;不对C不是C++.
结构标签不能自动生成类型.
参见问题2.
1.
2.
3一个结构可以包含指向自己的指针吗当然可以.
参见问题1.
6.
2.
4在C语言中实现抽象数据类型什么方法最好让客户使用指向没有公开定义(也许还隐藏在类型定义后边)的结构类型的指针是一个好办法.
只要不访问结构成员,声明和使用"匿名"结构指针(不完全结构类型指针)是合法的.
这也是使用抽象数据类型的原因.
2.
5在C中是否有模拟继承等面向对象程序设计特性的好方法把函数指针直接加入到结构中就可以实现简单的"方法".
你可以使用各种不雅而暴力的方法来实现继承,例如通过预处理器或含有"基类"的结构作为开始的子集,但这些方法都不完美.
很明显,也没有运算符的重载和覆盖(例如,"导出类"中的"方法"),那些必须人工去做.
显然的,如果你需要"真"的面向对象的程序设计,你需要使用一个支持这些特性的语言,例如C++.
7第2章结构、联合和枚举82.
6我遇到这样声明结构的代码:structname{intnamelen;charnamestr[1];};然后又使用一些内存分配技巧使namestr数组用起来好像有多个元素.
这样合法和可移植吗这种技术十分普遍,尽管DennisRitchie称之为"和C实现的无保证的亲密接触".
官方的解释认定它没有严格遵守C标准,尽管它看来在所有的实现中都可以工作.
仔细检查数组边界的编译器可能会发出警告.
另一种可能是把变长的元素声明为很大,而不是很小;在上例中:.
.
.
charnamestr[MAXSIZE];MAXSIZE比任何可能存储的name值都大.
但是,这种技术似乎也不完全符合标准的严格解释.
这些"亲密"结构都必须小心使用,因为只有程序员知道它的大小,而编译器却一无所知.
C99引入了"灵活数组域"概念,允许结构的最后一个域省略数组大小.
这为类似问题提供了一个圆满的解决方案.
参考资料:[Rationale,Sec.
3.
5.
4.
2];[C9X,Sec.
6.
5.
2.
1].
2.
7是否有自动比较结构的方法没有.
编译器没有简单的好办法实现结构比较(即,支持结构的==操作符),这也符合C的低层特性.
简单的按字节比较会由于结构中没有用到的"空洞"中的随机数据(参见问题2.
10)而失败;而按域比较在处理大结构时需要难以接受的大量重复代码.
如果你需要比较两个结构,你必须自己写函数按域比较.
参考资料:[K&R2,Sec.
6.
2p.
129];[Rationale,Sec.
3.
3.
9];[H&S,Sec.
5.
6.
2p.
133].
2.
8如何向接受结构参数的函数传入常数值传统的C没有办法生成匿名结构值;你必须使用临时结构变量或一个小的结构生成函数.
C99标准引入了"复合常量"(compoundliterals);复合常量的一种形式就可以允许结构常量.
例如,向假想plotpoint()函数传入一个坐标对常数,可以调用plotpoint((structpoint){1,2});与"指定初始值"(designatedinitializers)(C99的另一个功能)结合,也可以用成员名称确定成员值:plotpoint((structpoint){.
x=1,.
y=2});参见问题4.
6.
参考资料:[C9X,Sec.
6.
3.
2.
5,Sec.
6.
5.
8].
第2章结构、联合和枚举92.
9怎样从/向数据文件读/写结构用fwrite()写一个结构相对简单:fwrite(&somestruct,sizeofsomestruct,1,fp);对应的fread()调用可以再把它读回来.
但是这样写出的文件却不能移植(参见问题2.
10和20.
3).
同时注意如果结构包含任何指针,则只有指针值会被写入文件,当它们再次读回来的时候,很可能已经失效.
最后,为了广泛的移植,你必须用"b"标志打开文件;参见问题12.
30.
移植性更好的方案是写一对函数,用可移植(可能甚至是人可读)的方式按域读写结构,尽管开始可能工作量稍大.
参考资料:[H&S,Sec.
15.
13p.
381].
2.
10我的编译器在结构中留下了空洞,这导致空间浪费而且无法与外部数据文件进行"二进制"读写.
能否关掉填充,或者控制结构域的对齐方式这些"空洞"充当了"填充",为了保持结构中后面的域的对齐,这也许是必须的.
为了高效的访问,许多处理器喜欢(或要求)多字节对象(例如,结构中任何大于char的类型)不能处于随意的内存地址,而必须是2或4或对象大小的倍数.
编译器可能提供一种扩展用于这种控制(可能是#pragma;参见问题11.
21),但是没有标准的方法.
参见问题20.
3.
参考资料:[K&R2,Sec.
6.
4p.
138];[H&S,Sec.
5.
6.
4p.
135].
2.
11为什么sizeof返回的值大于结构的期望值,是不是尾部有填充为了确保分配连续的结构数组时正确对齐,结构可能有这种尾部填充.
即使结构不是数组的成员,填充也会保持,以便sizeof能够总是返回一致的大小.
参见问题2.
10.
参考资料:[H&S,Sec.
5.
6.
7pp.
139-40].
2.
12如何确定域在结构中的字节偏移ANSIC在中定义了osetof()宏,用osetof(structs,f)可以计算出域f在结构s中的偏移量.
如果出于某种原因,你需要自己实现这个功能,可以使用下边这样的代码:#defineoffsetof(type,f)((size_t)\((char*)&((type*)0)->f-(char*)(type*)0))这种实现不是100%的可移植;某些编译器可能会合法地拒绝接受.
参考资料:[ISO,Sec.
7.
1.
6];[Rationale,Sec.
3.
5.
4.
2];[H&S,Sec.
11.
1pp.
292-3].
第2章结构、联合和枚举102.
13怎样在运行时用名字访问结构中的域保持用osetof()(参见问题2.
12)计算的域偏移量.
如果structp是个结构实体的指针,而域f是个整数,它的偏移量是osetf,f的值可以间接地设置:*(int*)((char*)structp+offsetf)=value;2.
14程序运行正确,但退出时却"coredump"了,怎么回事问题程序:structlist{char*item;structlist*next;}/*这里是main程序*/main(argc,argv){.
.
.
}缺少的一个分号使main()被定义为返回一个结构.
由于中间的注释行,这个联系不容易看出来.
因为一般上,返回结构的函数在实现时,会加入一个隐含的返回指针,这个产生的main()函数代码试图接受三个参数,而实际上只有两个传入(这里,由C的启动代码传入).
参见问题10.
8和16.
4.
参考资料:[CT&P,Sec.
2.
3pp.
21-2].
2.
15可以初始化一个联合吗在原来的ANSIC中,只有联合中的第一个命名成员可以被初始化.
C99引入了"指定初始值",可以用来初始化任意成员.
参考资料:[K&R2,Sec.
6.
8pp.
148-9];[ISO,Sec.
6.
5.
7];[C9X,Sec.
6.
5.
8];[H&S,Sec.
4.
6.
7p.
100].
2.
16枚举和一组预处理的#dene有什么不同只有很小的区别.
C标准中允许枚举和其它整形类别自由混用而不会出错.
(但是,假如编译器不允许在未经明确类型转换的情况下混用这些类型,则聪明地使用枚举可以捕捉到某些程序错误.
)枚举的一些优点:自动赋值;调试器在检验枚举变量时,可以显示符号值;它们服从数据块作用域规则.
(编译器也可以对在枚举变量被任意地和其它类型混用时,产生非重要的警告信息,因为这被认为是坏风格.
)一个缺点是程序员不能控制这些对非重要的警告;有些程序员则反感于无法控制枚举变量的大小.
参考资料:[K&R2,Sec.
2.
3p.
39,Sec.
A4.
2p.
196];[ISO,Sec.
6.
1.
2.
5,Sec.
6.
5.
2,Sec.
6.
5.
2.
2,AnnexF];[H&S,Sec.
5.
5pp.
127-9,Sec.
5.
11.
2p.
153].
第2章结构、联合和枚举112.
17有什么容易的显示枚举值符号的方法没有.
你可以写一个小函数,把一个枚举常量值映射到字符串.
(为了调试的目的,一个好的调试器,应该可以自动显示枚举常量值符号.
)第2章结构、联合和枚举12第3章表达式3.
1为什么这样的代码:a[i]=i++;不能工作子表达式i++有一个副作用—–它会改变i的值—–由于i在同一表达式的其它地方被引用,这会导致无定义的结果,无从判断该引用(左边的a[i]中)是旧值还是新值.
(注意,尽管在K&R中建议这类表达式的行为不确定,但C标准却强烈声明它是无定义的,参见问题11.
32.
参考资料:[K&R1,Sec.
2.
12];[K&R2,Sec.
2.
12];[ISO,Sec.
6.
3];[H&S,Sec.
7.
12pp.
227-9].
3.
2使用我的编译器,下面的代码inti=7;printf("%d\n",i++*i++);返回49不管按什么顺序计算,难道不该打印出56吗尽管后缀自加和后缀自减操作符++和--在输出其旧值之后才会执行运算,但这里的"之后"常常被误解.
没有任何保证确保自增或自减会在输出变量原值之后和对表达式的其它部分进行计算之前立即进行.
也不能保证变量的更新会在表达式"完成"(按照ANSIC的术语,在下一个"序列点"之前,参见问题3.
7)之前的某个时刻进行.
本例中,编译器选择使用变量的旧值相乘以后再对二者进行自增运算.
包含多个不确定的副作用的代码的行为总是被认为未定义.
(简单而言,"多个不确定副作用"是指在同一个表达式中使用导致同一对象修改两次或修改以后又被引用的自增,自减和赋值操作符的任何组合.
这是一个粗略的定义;严格的定义参见问题3.
7,"未定义"的含义参见问题11.
32.
)甚至都不要试图探究这些东西在你的编译器中是如何实现的(这与许多C教科书上的弱智练习正好相反);正如K&R明智地指出,"如果你不知道它们在不同的机器上如何实现,这样的无知可能恰恰会有助于保护你.
"参考资料:[K&R1,Sec.
2.
12p.
50];[K&R2,Sec.
2.
12p.
54];[ISO,Sec.
6.
3];[H&S,Sec.
7.
12pp.
227-9];[CT&P,Sec.
3.
7p.
47];[PCS,Sec.
9.
5pp.
120-1].
13第3章表达式143.
3对于代码inti=3;i=i++;不同编译器给出不同的结果,有的为3,有的为4,哪个是正确的没有正确答案;这个表达式无定义.
参见问题3.
1,3.
7和11.
32.
同时注意,i++和++i都不同于i+1.
如果你要使i自增1,使用i=i+1,i+=1,i++或++i,而不是任何组合,参见问题3.
10.
3.
4这是个巧妙的表达式:a=b=a=b它不需要临时变量就可以交换a和b的值.
这不具有可移植性.
它试图在序列点之间两次修改变量a,而这是无定义的.
例如,有人报告如下代码:inta=123,b=7654;a^=b^=a^=b;在SCO优化C编译器(icc)下会把b置为123,把a置为0.
参见问题3.
1、3.
7和20.
14.
3.
5我可否用括号来强制执行我所需要的计算顺序一般来讲,不行.
运算符优先级和括弧只能赋予表达是计算部分的顺序.
在如下的代码中f()+g()*h()尽管我们知道乘法运算在加法之前,但这并不能说明这三个函数哪个会被首先调用.
如果你需要确保子表达式的计算顺序,你可能需要使用明确的临时变量和独立的语句.
参考资料:[K&R1,Sec.
2.
12p.
49,Sec.
A.
7p];[K&R2,Sec.
2.
12pp.
52-3,Sec.
A.
7p.
200.
].
3.
6可是&&和||运算符呢我看到过类似while((c=getchar())!
=EOF&&c!
='\n')的代码……这些运算符在此处有一个特殊的"短路"例外:如果左边的子表达式决定最终结果(即,真对于||和假对于&&)则右边的子表达式不会计算.
因此,从左至右的计算可以确保,对逗号表达式也是如此.
而且,所有这些运算符(包括:)都会引入一个额外的内部序列点(参见问题3.
7).
参考资料:[K&R1,Sec.
2.
6p.
38,Secs.
A7.
11-12pp.
190-1];[K&R2,Sec.
2.
6p.
41,Secs.
A7.
14-15pp.
207-8];[ISO,Sec.
6.
3.
13,Sec.
6.
3.
14,Sec.
6.
3.
15];[H&S,Sec.
7.
7pp.
217-8,Sec.
7.
8pp.
218-20,Sec.
7.
12.
1p.
229];[CT&P,Sec.
3.
7pp.
46-7].
第3章表达式153.
7我怎样才能理解复杂表达式"序列点"是什么序列点是一个时间点(在整个表达式全部计算完毕之后或在或逗号运算符处,或在函数调用之前),此刻尘埃落定,所有的副作用都已确保结束.
ANSI/ISOC标准这样描述:在上一个和下一个序列点之间,一个对象所保存的值至多只能被表达式的计算修改一次.
而且前一个值只能用于决定将要保存的值.
第二句话比较费解.
它说在一个表达式中如果某个对象需要写入,则在同一表达式中对该对象的访问应该只局限于直接用于计算将要写入的值.
这条规则有效地限制了只有能确保在修改之前才访问变量的表达式为合法.
例如i=i+1合法,而a[i]=i++则非法(参见问题3.
1).
参见下边的问题3.
8.
参考资料:[ISO,Sec.
5.
1.
2.
3,Sec.
6.
3,Sec.
6.
6,AnnexC];[Rationale,Sec.
2.
1.
2.
3];[H&S,Sec.
7.
12.
1pp.
228-9].
3.
8那么,对于a[i]=i++;我们不知道a[]的哪一个分量会被改写,但i的确会增加1,对吗不一定!
如果一个表达式和程序变得未定义,则它的所有方面都会变成未定义.
参见问题3.
2,3.
3,11.
32和11.
35.
3.
9++i和i++有什么区别如果你的C语言书没有说明它们的区别,那么买一本好的.
简单而言:++i在i存储的值上增加一并向使用它的表达式"返回"新的,增加后的值;而i++对i增加一,但返回原来的是未增加的值.
3.
10如果我不使用表达式的值,我应该用++i或i++来自增一个变量吗由于这两种格式区别仅在于生成的值,所以在仅使用它们的副作用时,二者完全一样.
但是,在C++中,前缀方式却是首选.
参见问题3.
3.
3.
11为什么如下的代码inta=100,b=100;longintc=a*b;不能工作根据C的内部类型转换规则,乘法是用int进行的,而其结果可能在转换为long型并赋给左边的c之前溢出或被截短.
可以使用明确的类型转换,强迫乘法以long型进行:longintc=(longint)a*b;第3章表达式16注意,(longint)(a*b)不能达到需要的效果.
当两个整数做除法而结果赋与一个浮点变量时,也有可能有同样类型的问题,解决方法也是类似的.
参考资料:[K&R1,Sec.
2.
7p.
41];[K&R2,Sec.
2.
7p.
44];[ISO,Sec.
6.
2.
1.
5];[H&S,使用我的编辑器,下面的代码];[CT&P,Sec.
3.
9pp.
49-50].
3.
12我需要根据条件把一个复杂的表达式赋值给两个变量中的一个.
可以用下边这样的代码吗((condition)a:b)=compli-catedexpression;不能.
:操作符,跟多数操作符一样,生成一个值,而不能被赋值.
换言之,:不能生成一个"左值".
如果你真的需要,你可以试试下面这样的代码:*((condition)&a:&b)=complicated_expression;尽管这毫无优雅可言.
参考资料:[ISO,Sec.
6.
3.
15];[H&S,Sec.
7.
1pp.
179-180].
第4章指针4.
1我想声明一个指针并为它分配一些空间,但却不行.
这些代码有什么问题char*p;*p=malloc(10);你所声明的指针是p,而不是*p,当你操作指针本身时(例如当你对其赋值,使之指向别处时),你只需要使用指针的名字即可:p=malloc(10);当你操作指针指向的内存时,你才需要使用*作为间接操作符:*p='H';参见问题1.
7,7.
1,7.
5和8.
3.
参考资料:[CT&P,Sec.
3.
1p.
28].
4.
2*p++自增p还是p所指向的变量后缀++和--操作符本质上比前缀一目操作的优先级高,因此*p++和*(p++)等价,它自增p并返回p自增之前所指向的值.
要自增p指向的值,使用(*p)++,如果副作用的顺序无关紧要也可以使用++*p.
参考资料:[K&R1,Sec.
5.
1p.
91];[K&R2,Sec.
5.
1p.
95];[ISO,Sec.
6.
3.
2,Sec.
6.
3.
3];[H&S,Sec.
7.
4.
4pp.
192-3,Sec.
7.
5p.
193,Secs.
7.
5.
7,7.
5.
8pp.
199-200].
4.
3我有一个char*型指针正巧指向一些int型变量,我想跳过它们.
为什么如下的代码((int*)p)++;不行在C语言中,类型转换意味着"把这些二进制位看作另一种类型,并作相应的对待";这是一个转换操作符,根据定义它只能生成一个右值(rvalue).
而右值既不能赋值,也不能用++自增.
(如果编译器支持这样的扩展,那要么是一个错误,要么是有意作出的非标准扩展.
)要达到你的目的可以用:p=(char*)((int*)p+1);或者,因为p是char*型,直接用p+=sizeof(int);但是,在可能的情况下,你还是应该首先选择适当的指针类型,而不是一味地试图李代桃僵.
参考资料:[K&R2,Sec.
A7.
5p.
205];[ISO,Sec.
6.
3.
4];[Rationale,Sec.
3.
3.
2.
4];[H&S,Sec.
7.
1pp.
179-80].
17第4章指针184.
4我有个函数,它应该接受并初始化一个指针voidf(int*ip){staticintdummy=5;ip=&dummy;}但是当我如下调用时:int*ip;f(ip);调用者的指针却没有任何变化.
你确定函数初始化的是你希望它初始化的东西吗请记住在C中,参数是通过值传递的.
被调函数仅仅修改了传入的指针副本.
你需要传入指针的地址(函数变成接受指针的指针),或者让函数返回指针.
参见问题4.
5和4.
7.
4.
5我能否用void**指针作为参数,使函数按引用接受一般指针不可移植.
C中没有一般的指针的指针类型.
void*可以用作一般指针只是因为当它和其它类型相互赋值的时候,如果需要,它可以自动转换成其它类型;但是,如果试图这样转换所指类型为void*之外的类型的void**指针时,这个转换不能完成.
4.
6我有一个函数externintf(int*);它接受指向int型的指针.
我怎样用引用方式传入一个常数下面这样的调用f(&5);似乎不行.
在C99中,你可以使用"复合常量":f((int[]){5});在C99之前,你不能直接这样做;你必须先定义一个临时变量,然后把它的地址传给函数:intfive=5;f(&five);参见问题2.
8,4.
4和20.
1.
4.
7C有"按引用传递"吗真的没有.
严格地讲,C总是按值传递.
你可以自己模拟按引用传递,定义接受指针的函数,然后在调用时使用&操作符.
事实上,当你向函数传入数组(传入指针的情况参见问题6.
4及其它)时,编译器本质上就是在模拟按引用传递.
但是C没有任何真正等同于正式的按引用传递或C++的引用参数的东西.
另一方面,类似函数的预处理宏可以提供一种"按名称传递"的形式.
参见问题4.
4和20.
1.
参考资料:[K&R1,Sec.
1.
8pp.
24-5,Sec.
5.
2pp.
91-3];[K&R2,Sec.
1.
8pp.
27-8,Sec.
5.
2pp.
95-7];[ISO,Sec.
6.
3.
2.
2];[H&S,Sec.
9.
5pp.
273-4].
第4章指针194.
8我看到了用指针调用函数的不同语法形式.
到底怎么回事最初,一个函数指针必须用*操作符(和一对额外的括弧)"转换为"一个"真正的"函数才能调用:intr,func(),(*fp)()=func;r=(*fp)();而函数总是通过指针进行调用的,所有"真正的"函数名总是隐式的退化为指针(在表达式中,正如在初始化时一样.
参见问题1.
14).
这个推论表明无论fp是函数名和函数的指针r=fp();ANSIC标准实际上接受后边的解释,这意味着*操作符不再需要,尽管依然允许.
参见问题1.
14.
参考资料:[K&R1,Sec.
5.
12p.
116];[K&R2,Sec.
5.
11p.
120];[ISO,Sec.
6.
3.
2.
2];[Rationale,Sec.
3.
3.
2.
2];[H&S,Sec.
5.
8p.
147,Sec.
7.
4.
3p.
190].
4.
9我怎样把一个int变量转换为char*型我试了类型转换,但是不行.
这取决于你希望做什么.
如果你的类型转换不成功,你可能是企图把整数转为字符串,这种情况参见问题13.
1.
如果你试图把整数转换为字符,参见问题8.
4.
如果你试图让一个指针指向特定的内存地址,参见问题19.
29.
第4章指针20第5章空(null)指针5.
1臭名昭著的空指针到底是什么语言定义中说明,每一种指针类型都有一个特殊值——"空指针"——它与同类型的其它所有指针值都不相同,它"与任何对象或函数的指针值都不相等".
也就是说,取地址操作符&永远也不能得到空指针,同样对malloc()的成功调用也不会返回空指针,如果失败,malloc()的确返回空指针,这是空指针的典型用法:表示"未分配"或者"尚未指向任何地方"的指针.
空指针在概念上不同于未初始化的指针.
空指针可以确保不指向任何对象或函数;而未初始化指针则可能指向任何地方.
参见问题1.
10、7.
1和7.
26.
如上文所述,每种指针类型都有一个空指针,而不同类型的空指针的内部表示可能不尽相同.
尽管程序员不必知道内部值,但编译器必须时刻明确需要那种空指针,以便在需要的时候加以区分(参见问题5.
2、5.
5和5.
6).
参考资料:[K&R1,Sec.
5.
4pp.
97-8];[K&R2,Sec.
5.
4p.
102];[ISO,Sec.
6.
2.
2.
3];[Rationale,Sec.
3.
2.
2.
3];[H&S,Sec.
5.
3.
2pp.
121-3].
5.
2怎样在程序里获得一个空指针根据语言定义,在指针上下文中的常数0会在编译时转换为空指针.
也就是说,在初始化、赋值或比较的时候,如果一边是指针类型的值或表达式,编译器可以确定另一边的常数0为空指针并生成正确的空指针值.
因此下边的代码段完全合法:char*p=0;if(p!
=0)参见问题5.
3.
然而,传入函数的参数不一定被当作指针环境,因而编译器可能不能识别未加修饰的0"表示"指针.
在函数调用的上下文中生成空指针需要明确的类型转换,强制把0看作指针.
例如,Unix系统调用execl接受变长的以空指针结束的字符指针参数.
它应该如下正确调用:execl("/bin/sh","sh","-c","date",(char*)0);如果省略最后一个参数的(char*)转换,则编译器无从知道这是一个空指针,从而当作一个0传入.
(注意很多Unix手册在这个例子上都弄错了.
)21第5章空(null)指针22如果范围内有函数原型,则参数传递变为"赋值上下文",从而可以安全省略多数类型转换,因为原型告知编译器需要指针,使之把未加修饰的0正确转换为适当的指针.
函数原型不能为变长参数列表中的可变参数提供类型.
(参见问题15.
3)在函数调用时对所有的空指针进行类型转换可能是预防可变参数和无原型函数出问题的最安全的办法.
摘要:可以使用未加修饰的0:需要显示的类型转换:初始化函数调用,作用域内无原型赋值变参函数调用中的可变参数比较固定参数的函数调用且在作用域内有原型参考资料:[K&R1,Sec.
A7.
7p.
190,Sec.
A7.
14p.
192];[K&R2,Sec.
A7.
10p.
207,Sec.
A7.
17p.
209];[ISO,Sec.
6.
2.
2.
3];[H&S,Sec.
4.
6.
3p.
95,Sec.
6.
2.
7p.
171].
5.
3用缩写的指针比较"if(p)"检查空指针是否可靠如果空指针的内部表达不是0会怎么样当C在表达式中要求布尔值时,如果表达式等于0则认为该值为假,否则为真.
换言之,只要写出if(expr)无论"expr"是任何表达式,编译器本质上都会把它当if((expr)!
=0)处理.
如果用指针p代替"expr"则if(p)等价于if(p!
=0).
而这是一个比较上下文,因此编译器可以看出0实际上是一个空指针常数,并使用正确的空指针值.
这里没有任何欺骗;编译器就是这样工作的,并为、二者生成完全一样的代码.
空指针的内部表达无关紧要.
布尔否操作符!
可如下描述:!
expr本质上等价于(expr)0:1或等价于((expr)==0)从而得出结论if(!
p)等价于if(p==0)类似if(p)这样的"缩写",尽管完全合法,但被一些人认为是不好的风格(另外一些人认为恰恰是好的风格;参见问题17.
8).
参见问题9.
2.
参考资料:[K&R2,Sec.
A7.
4.
7p.
204];[ISO,Sec.
6.
3.
3.
3,Sec.
6.
3.
9,Sec.
6.
3.
13,Sec.
6.
3.
14,Sec.
6.
3.
15,Sec.
6.
6.
4.
1,Sec.
6.
6.
5];[H&S,Sec.
5.
3.
2p.
122].
第5章空(null)指针235.
4NULL是什么,它是怎么定义的作为一种风格,很多人不愿意在程序中到处出现未加修饰的0.
因此定义了预处理宏NULL(在和其它几个头文件中)为空指针常数,通常是0或者((void*)0)(参见问题5.
6).
希望区别整数0和空指针0的人可以在需要空指针的地方使用NULL.
使用NULL只是一种风格习惯;预处理器把所有的NULL都还原回0,而编译还是依照上文的描述处理指针上下文的0.
特别是,在函数调用的参数里,NULL之前(正如在0之前)的类型转换还是需要.
问题5.
2下的表格对0和NULL都有效(带修饰的NULL和带修饰的0完全等价).
NULL只能用作指针常数;参见问题5.
7.
参考资料:[K&R1,Sec.
5.
4pp.
97-8];[K&R2,Sec.
5.
4p.
102];[ISO,Sec.
7.
1.
6,Sec.
6.
2.
2.
3];[Rationale,Sec.
4.
1.
5];[H&S,Sec.
5.
3.
2p.
122,Sec.
11.
1p.
292].
5.
5在使用非全零作为空指针内部表达的机器上,NULL是如何定义的跟其它机器一样:定义为0(或某种形式的0;参见问题5.
4).
当程序员请求一个空指针时,无论写"0"还是"NULL",都是有编译器来生成适合机器的空指针的二进制表达形式.
因此,在空指针的内部表达不为0的机器上定义NULL为0跟在其它机器上一样合法:编译器在指针上下文看到的未加修饰的0都会被生成正确的空指针.
参见问题5.
2、5.
8和5.
14.
参考资料:[ISO,Sec.
7.
1.
6];[Rationale,Sec.
4.
1.
5].
5.
6如果NULL定义成#deneNULL((char*)0)难道不就可以向函数传入不加转换的NULL了吗一般情况下,不行.
复杂之处在于,有的机器不同类型数据的指针有不同的内部表达.
这样的NULL定义对于接受字符指针的的函数没有问题,但对于其它类型的指针参数仍然有问题(在缺少原型的情况下),而合法的构造如FILE*fp=NULL;则会失败.
不过,ANSIC允许NULL的可选定义#defineNULL((void*)0)除了潜在地帮助错误程序运行(仅限于使用同样类型指针的机器,因此帮助有限)以外,这样的定义还可以发现错误使用NULL的程序(例如,在实际需要使用ASCIINUL字符的地方;参见问题5.
7).
无论如何,ANSI函数原型确保大多数(尽管不是全部;参见问题5.
2)指针参数在传入函数时正确转换.
因此,这个问题有些多余.
参考资料:[Rationale,Sec.
4.
1.
5].
第5章空(null)指针245.
7如果NULL和0作为空指针常数是等价的,那我到底该用哪一个呢许多程序员认为在所有的指针上下文中都应该使用NULL,以表明该值应该被看作指针.
另一些人则认为用一个宏来定义0,只不过把事情搞得更复杂,反而令人困惑.
因而倾向于使用未加修饰的0.
没有正确的答案.
(参见问题9.
2和17.
8)C程序员应该明白,在指针上下文中NULL和0是完全等价的,而未加修饰的0也完全可以接受.
任何使用NULL(跟0相对)的地方都应该看作一种温和的提示,是在使用指针;程序员(和编译器都)不能依靠它来区别指针0和整数0.
在需要其它类型的0的时候,即便它可能工作也不能使用NULL,因为这样做发出了错误的格式信息.
(而且,ANSI允许把NULL定义为((void*)0),这在非指针的上下文中完全无效.
特别是,不能在需要ASCII空字符(NUL)的地方用NULL.
如果有必要,提供你自己的定义#defineNUL'\0'参考资料:[K&R1,Sec.
5.
4pp.
97-8];[K&R2,Sec.
5.
4p.
102].
5.
8但是如果NULL的值改变了,比如在使用非零内部空指针的机器上,难道用NULL(而不是0)不是更好吗不.
(用NULL可能更好,但不是这个原因.
)尽管符号常量经常代替数字使用以备数字的改变,但这不是用NULL代替0的原因.
语言本身确保了源码中的0(用于指针上下文)会生成空指针.
NULL只是用作一种格式习惯.
参见问题5.
5和9.
2.
5.
9用预定义宏#deneNullptr(type)(type*)0帮助创建正确类型的空指针.
这种技巧,尽管很流行而且表面上看起来很有吸引力,但却没有多少意义.
在赋值和比较时它并不需要;参见问题5.
2.
它甚至都不能节省键盘输入.
参见问题9.
1和10.
1.
5.
10这有点奇怪.
NULL可以确保是0,但空(null)指针却不一定随便使用术语"null"或"NULL"时,可能意味着以下一种或几种含义:1.
概念上的空指针,问题5.
1定义的抽象语言概念.
它使用以下的东西实现的……2.
空指针的内部(或运行期)表达形式,这可能并不是全零,而且对不用的指针类型可能不一样.
真正的值只有编译器开发者才关心.
C程序的作者永远看不到它们,因为他们使用……第5章空(null)指针253.
空指针常数,这是一个常整数0(参见问题5.
2).
它通常隐藏在……4.
NULL宏,它被定义为0(参见问题5.
4).
最后转移我们注意力到……5.
ASCII空字符(NUL),它的确是全零,但它和空指针除了在名称上以外,没有任何必然关系;而……6.
"空串"(nullstring),它是内容为空的字符串("").
在C中使用空串这个术语可能令人困惑,因为空串包括空字符('\0'),但不包括空指针,这让我们绕了一个完整的圈子……本文用短语"空指针"("nullpointer",小写)表示第一种含义,标识"0"或短语"空指针常数"表示含义3,用大写NULL表示含义4.
5.
11为什么有那么多关于空指针的疑惑为什么这些问题如此经常地出现C程序员传统上喜欢知道很多(可能比他们需要知道的还要多)关于机器实现的细节.
空指针在源码和大多数机器实现中都用零来表示的事实导致了很多无根据的猜测.
而预处理宏(NULL)的使用又似乎在暗示这个值可能在某个时刻或者在某种怪异的机器上会改变.
"if(p==0)"这种结构又很容易被误认为在比较之前把p转成了整数类型,而不是把0转成了指针类型.
最后,术语"空"的几种用法(如上文问题5.
10所列出的)之间的区别又可能被忽视.
冲出这些迷惘的一个好办法是想象C使用一个关键字(或许象Pascal那样,用"nil")作为空指针常数.
编译器要么在源代码没有歧义的时候把"nil"转成适当类型的空指针,或者有歧义的时候发出提示.
现在事实上,C语言的空指针常数关键字不是"nil"而是"0",这在多数情况下都能正常工作,除了一个未加修饰的"0"用在非指针上下文的时候,编译器把它生成整数0而不是发出错误信息,如果那个未加修饰的0是应该是空指针常数,那么生成的程序不行.
5.
12我很困惑.
我就是不能理解这些空指针一类的东西.
有两条简单规则你必须遵循:1.
当你在源码中需要空指针常数时,用"0"或"NULL".
2.
如果在函数调用中"0"或"NULL"用作参数,把它转换成被调函数需要的指针类型讨论的其它内容是关于别人的误解,关于空指针的内部表达(这你无需了解),和关于函数原型的复杂性的.
(考虑到这些复杂性,我们发现规则2有些保守;但它没什么害处.
)理解问题5.
1、5.
2和5.
4,考虑问题5.
3、5.
7、5.
10和5.
11你就就会变得清晰.
第5章空(null)指针265.
13考虑到有关空指针的所有这些困惑,难道把要求它们内部表达都必须为0不是更简单吗如果没有其它的原因,这样做会是没脑筋的.
因为它会不必要地限制某些实现,阻止它们用特殊的非全零值表达空指针,尤其是当那些值可以为非法访问引发自动的硬件陷阱的时候.
况且,这样的要求真正完成了什么呢对空指针的正确理解不需要内部表达的知识,无论是零还是非零.
假设空指针内部表达为零不会使任何代码的编写更容易(除了一些不动脑筋的calloc()调用;参见问题7.
26).
用零作空指针的内部表达也不能消除在函数调用时的类型转换,因为指针的大小可能和int型的大小依然不同.
(如果象上文问题5.
11所述,用"nil"来请求空指针,则用0作空指针的内部表达的想法都不会出现.
)5.
14说真的,真有机器用非零空指针吗,或者不同类型用不同的表达至少PL/I,Prime50系列用段07777,偏移0作为空指针.
后来的型号使用段0,偏移0作为C的空指针,迫使类似TCNP(测试C空指针)的指令明显地成了现成的作出错误猜想的蹩脚C代码.
旧些的按字寻址的Prime机器同样因为要求字节指针(char*)比字指针(int*)长而臭名昭著.
DataGeneral的EclipseMV系列支持三种结构的指针格式(字、字节和比特指针),C编译器使用了其中之二:char*和void*使用字节指针,而其它的使用字指针.
某些Honeywell-Bull大型机使用比特模式06000作为(内部的)空指针.
CDCCyber180系列使用包含环(ring),段和位移的48位指针.
多数用户(在环11上)使用的空指针为0xB00000000000.
在旧的1次补码的CDC机器上用全1表示各种数据,包括非法指针,是十分常见的事情.
旧的HP3000系列对字节地址和字地址使用不同的寻址模式;正如上面的机器一样,它因此也使用不同的形式表达char*和void*型指针及其它指针.
SymbolicsLisp机器是一种标签结构,它甚至没有传统的数字指针;它使用对(通常是不存在的句柄)作为C空指针.
根据使用的"内存模式",8086系列处理器(PC兼容机)可能使用16位的数据指针和32位的函数指针,或者相反.
一些64位的Cray机器在一个字的低48位表示int*;char*使用高16位的某些位表示一个字节在一个字中的偏移.
参考资料:[K&R1,Sec.
A14.
4p.
211].
5.
15运行时的"空指针赋值"错误是什么意思这个信息,通常由MS-DOS编译器发出,表明你通过空指针向非法地址(可能是缺省数据段的偏移0位置)写入了数据.
参见问题16.
7.
第6章数组和指针6.
1我在一个源文件中定义了chara[6],在另一个中声明了externchar*a.
为什么不行你在一个源文件中定义了一个字符串,而在另一个文件中定义了指向字符的指针.
externchar*的申明不能和真正的定义匹配.
类型T的指针和类型T的数组并非同种类型.
请使用externchara[].
参考资料:[ISO,Sec.
6.
5.
4.
2];[CT&P,Sec.
3.
3pp.
33-4,Sec.
4.
5pp.
64-5].
6.
2可是我听说chara[]和char*a是一样的.
并非如此.
(你所听说的应该跟函数的形式参数有关;参见问题6.
4)数组不是指针.
数组定义chara[6]请求预留6个字符的位置,并用名称"a"表示.
也就是说,有一个称为"a"的位置,可以放入6个字符.
而指针申明char*p,请求一个位置放置一个指针,用名称"p"表示.
这个指针几乎可以指向任何位置:任何字符和任何连续的字符,或者哪里也不指(参见问题5.
1和1.
10).
一个图形胜过千言万语.
声明chara[]="hello";char*p="world";将会初始化下图所示的数据结果:a:|h|e|l|l|o|\0|p:w|o|r|l|d|\0|根据x是数组还是指针,类似x[3]这样的引用会生成不同的代码.
认识到这一点大有裨益.
以上面的声明为例,当编译器看到表达式a[3]的时候,它生成代码从a的位置开始跳过3个,然后取出那个字符.
如果它看到p[3],它生成代码找到"p"的位置,取出其中的指针值,在指针上加3然后取出指向的字符.
换言之,a[3]是名为a的对象(的起始位置)之后3个位置的值,而p[3]是p指向的对象的3个位置之后的值.
在上例中,a[3]和p[3]碰巧都是'l',但是编译器到达那里的途径不尽相同.
本质的区别在于类似a的数组和类似p的指针一旦在表达式中出现就会27第6章数组和指针28按照不同的方法计算,不论它们是否有下标.
下一问题继续深入解释.
参见问题1.
13.
参考资料:[K&R2,Sec.
5.
5p.
104];[CT&P,Sec.
4.
5pp.
64-5].
6.
3那么,在C语言中"指针和数组等价"到底是什么意思在C语言中对数组和指针的困惑多数都来自这句话.
说数组和指针"等价"不表示它们相同,甚至也不能互换.
它的意思是说数组和指针的算法定义可以用指针方便的访问数组或者模拟数组.
特别地,等价的基础来自这个关键定义:一个T的数组类型的左值如果出现在表达式中会蜕变为一个指向数组第一个成员的指针(除了三种例外情况);结果指针的类型是T的指针.
这就是说,一旦数组出现在表达式中,编译器会隐式地生成一个指向数组第一个成员地指针,就像程序员写出了&a[0]一样.
例外的情况是,数组为sizeof或&操作符的操作数,或者为字符数组的字符串初始值.
作为这个这个定义的后果,编译器并那么不严格区分数组下标操作符和指针.
在形如a[i]的表达式中,根据上边的规则,数组蜕化为指针然后按照指针变量的方式如p[i]那样寻址,如问题6.
2所述,尽管最终的内存访问并不一样.
如果你把数组地址赋给指针:p=a;那么p[3]和a[3]将会访问同样的成员.
参见问题6.
6和6.
11.
参考资料:[K&R1,Sec.
5.
3pp.
93-6];[K&R2,Sec.
5.
3p.
99];[ISO,Sec.
6.
2.
2.
1,Sec.
6.
3.
2.
1,Sec.
6.
3.
6];[H&S,Sec.
5.
4.
1p.
124].
6.
4那么为什么作为函数形参的数组和指针申明可以互换呢这是一种便利.
由于数组会马上蜕变为指针,数组事实上从来没有传入过函数.
允许指针参数声明为数组只不过是为让它看起来好像传入了数组,因为该参数可能在函数内当作数组使用.
特别地,任何声明"看起来象"数组的参数,例如voidf(chara[]){.
.
.
}在编译器里都被当作指针来处理,因为在传入数组的时候,那正是函数接收到的.
voidf(char*a){.
.
.
}这种转换仅限于函数形参的声明,别的地方并不适用.
如果这种转换令你困惑,请避免它;很多程序员得出结论,让形参声明"看上去象"调用或函数内的用法所带来的困惑远远大于它所提供的方便.
第6章数组和指针29参见问题6.
18.
参考资料:[K&R1,Sec.
5.
3p.
95,Sec.
A10.
1p.
205];[K&R2,Sec.
5.
3p.
100,Sec.
A8.
6.
3p.
218,Sec.
A10.
1p.
226];[ISO,Sec.
6.
5.
4.
3,Sec.
6.
7.
1,Sec.
6.
9.
6];[H&S,Sec.
9.
3p.
271];[CT&P,Sec.
3.
3pp.
33-4].
6.
5如果你不能给它赋值,那么数组如何能成为左值呢ANSIC标准定义了"可变左值",而数组不是.
参考资料:[ISO,Sec.
6.
2.
2.
1];[Rationale,Sec.
3.
2.
2.
1];[H&S,Sec.
7.
1p.
179].
6.
6现实地讲,数组和指针地区别是什么数组自动分配空间,但是不能重分配或改变大小.
指针必须明确赋值以指向分配的空间(可能使用malloc),但是可以随意重新赋值(即,指向不同的对象),同时除了表示一个内存块的基址之外,还有许多其它的用途.
由于数组和指针所谓的等价性(参见问题6.
3),数组和指针经常看起来可以互换,而事实上指向malloc分配的内存块的指针通常被看作一个真正的数组(也可以用[]引用).
参见问题6.
11和6.
13.
但是,要小心sizeof.
参见问题1.
13和20.
12.
6.
7有人跟我讲,数组不过是常指针.
这有些过度单纯化了.
数组名之所以为"常数"是因为它不能被赋值,但是数组不是指针,问题6.
2的讨论和图画可以说明这个.
参见问题6.
3和6.
6.
6.
8我遇到一些"搞笑"的代码,包含5["abcdef"]这样的"表达式".
这为什么是合法的C表达式呢是的,弗吉尼亚1,数组和下标在C语言中可以互换.
这个奇怪的事实来自数组下标的指针定义,即对于任何两个表达式a和e,只要其中一个是指针表达式而另一个为整数,则a[e]和*((a)+(e))完全一样.
这种交换性在许多C语言的书中被看作值得骄傲的东西,但是它除了在混乱C语言竞赛之外,其实鲜有用武之地.
参考资料:[Rationale,Sec.
3.
3.
2.
1];[H&S,Sec.
5.
4.
1p.
124,Sec.
7.
4.
1pp.
186-7].
1这里有个美国典故,在1897年,有个叫弗吉尼亚(Virginia)的八岁小女孩,她对圣诞老人是否存在感到困惑,因而写了封寻问信给《纽约太阳报》,于是就有了"Yes,Virginia,thereisaSantaClaus"这篇评论,有兴趣的朋友可以在http://www.
hymnsandcarolsofchristmas.
com/santa/virginia'squestion.
htm找到相关资料.
第6章数组和指针306.
9既然数组引用会蜕化为指针,如果arr是数组,那么arr和&arr又有什么区别呢区别在于类型.
在标准C中,&arr生成一个"T型数组"的指针,指向整个数组.
在ANSI之前的C中,&arr中的&通常会引起一个警告,它通常被忽略.
在所有的C编译器中,对数组的简单引用(不包括&操作符)生成一个T的指针类型的指针,指向数组的第一成员.
参见问题6.
3,6.
10和6.
15.
参考资料:[ISO,Sec.
6.
2.
2.
1,Sec.
6.
3.
3.
2];[Rationale,Sec.
3.
3.
3.
2];[H&S,Sec.
7.
5.
6p.
198].
6.
10我如何声明一个数组指针通常,你不需要.
当人们随便提到数组指针的时候,他们通常想的是指向它的第一个元素的指针.
考虑使用指向数组某个元素的指针,而不是数组的指针.
类型T的数组蜕变成类型T的指针(参见问题6.
3),这很方便;在结果的指针上使用下标或增量就可以访问数组中单独的成员.
而真正的数组指针,在使用下标或增量时,会跳过整个数组,通常只在操作数组的数组时有用——如果还有一点用的话.
参见问题6.
15.
如果你真的需要声明指向整个数组的指针,使用类似"int(*ap)[N];"这样的声明.
其中N是数组的大小(参见问题1.
7).
如果数组的大小未知,原则上可以省略N,但是这样生成的类型,"指向大小未知的数组的指针",毫无用处.
参见问题6.
9.
参考资料:[ISO,Sec.
6.
2.
2.
1].
6.
11我如何在运行期设定数组的大小我怎样才能避免固定大小的数组由于数组和指针的等价性(参见问题6.
3),可以用指向malloc分配的内存的指针来模拟数组.
执行#includeint*dynarray;dynarray=malloc(10*sizeof(int));以后(如果malloc调用成功),你可以象传统的静态分配的数组那样引用dynarry[i](i从0到9).
唯一的区别是sizeof不能给出"数组"的大小.
参见问题1.
12、6.
13和7.
9.
6.
12我如何声明大小和传入的数组一样的局部数组直到最近以前,你都不能;C语言的数组维度一直都是编译时常数.
但是,C99引入了变长数组(VLA),解决了这个问题;局部数组的大小可以用变量或其它表达第6章数组和指针31式设置,可能也包括函数参数.
(gcc提供参数化数组作为扩展已经有些时候了.
)如果你不能使用C99或gcc,你必须使用malloc(),并在函数返回之前调用free().
参见问题6.
11,6.
13,6.
16,7.
19和7.
27.
参考资料:[ISO,Sec.
6.
4,Sec.
6.
5.
4.
2];[C9X,Sec.
6.
5.
5.
2].
6.
13我该如何动态分配多维数组传统的解决方案是分配一个指针数组,然后把每个指针初始化为动态分配的"列".
以下为一个二维的例子:#includeint**array1=malloc(nrows*sizeof(int*));for(i=0;i=NCOLUMNS时,访问&array[0][0][x]的结果未定义.
C99允许变长数组,一旦接受C99扩展的编译器广泛流传以后,VLA可能是首选的解决方案.
gcc支持可变数组已经有些时日了.
当你需要使用各种大小的多维数组的函数时,一种解决方案是象问题6.
13那样动态模拟所有的数组.
参见问题6.
15,6.
17,6.
12.
参考资料:[ISO,Sec.
6.
3.
6];[C9X,Sec.
6.
5.
5.
2].
6.
17我怎样在函数参数传递时混用静态和动态多维数组没有完美的方法.
假设有如下声明intarray[NROWS][NCOLUMNS];int**array1;/*不齐的*/int**array2;/*连续的*/int*array3;/*"变平的"*/int(*array4)[NCOLUMNS];指针的初始值如问题6.
13的程序片段,函数声明如下voidf1a(inta[][NCOLUMNS],intnrows,intncolumns);voidf1b(int(*a)[NCOLUMNS],intnrows,intncolumns);voidf2(int*aryp,intnrows,intncolumns);voidf3(int**pp,intnrows,intncolumns);其中f1a()和f1b()接受传统的二维数组,f2()接受"扁平的"二维数组,f3()接受指针的指针模拟的数组(参见问题6.
15和6.
16),下面的调用应该可以如愿运行:f1a(array,NROWS,NCOLUMNS);f1b(array,NROWS,NCOLUMNS);f1a(array4,nrows,NCOLUMNS);f1b(array4,nrows,NCOLUMNS);f2(&array[0][0],NROWS,NCOLUMNS);f2(*array,NROWS,NCOLUMNS);f2(*array2,nrows,ncolumns);f2(array3,nrows,ncolumns);f2(*array4,nrows,NCOLUMNS);f3(array1,nrows,ncolumns);f3(array2,nrows,ncolumns);下面的调用在大多数系统上可能可行,但是有可疑的类型转换,而且只有动态ncolumns和静态NCOLUMNS匹配才行:第6章数组和指针34f1a((int(*)[NCOLUMNS])(*array2),nrows,ncolumns);f1a((int(*)[NCOLUMNS])(*array2),nrows,ncolumns);f1b((int(*)[NCOLUMNS])array3,nrows,ncolumns);f1b((int(*)[NCOLUMNS])array3,nrows,ncolumns);同时必须注意向f2()传递&array[0][0](或者等价的*array)并不完全符合标准;参见问题6.
16.
如果你能理解为何上述调用可行且必须这样书写,而未列出的组合不行,那么你对C语言中的数组和指针就有了很好的理解了.
为免受这些东西的困惑,一种使用各种大小的多维数组的办法是令它们"全部"动态分配,如问题6.
13所述.
如果没有静态多维数组——如果所有的数组都按问题6.
13的array1和array2分配——那么所有的函数都可以写成f3()的形式.
6.
18当数组是函数的参数时,为什么sizeof不能正确报告数组的大小编译器把数组参数当作指针对待(参见问题6.
4),因而报告的时指针的大小.
参考资料:[H&S,Sec.
7.
5.
2p.
195].
第7章内存分配7.
1为什么这段代码不行char*answer;printf("Typesomething:\n");gets(answer);printf("Youtyped\"%s\"\n",answer);指针变量answer,传入gets(),意在指向保存得到的应答的位置,但却没有指向任何合法的位置.
换言之,我们不知道指针answer指向何处.
因为局部变量没有初始化,通常包含垃圾信息,所以甚至都不能保证answer是一个合法的指针.
参见问题1.
10和5.
1.
改正提问程序的最简单方案是使用局部数组,而不是指针,让编译器考虑分配的问题:#include#includecharanswer[100],*p;printf("Typesomething:\n");fgets(answer,sizeofanswer,stdin);if((p=strchr(answer,'\n'))!
=NULL)*p='\0';printf("Youtyped\"%s\"\n",answer);本例中同时用fgets()代替gets(),以便array的结束符不被改写.
参见问题12.
20.
不幸的是,本例中的fgets()不会象gets()那样自动地去掉结尾的\n.
也可以用malloc()分配answer缓冲区.
7.
2我的strcat()不行.
我试了char*s1="Hello,";char*s2="world!
";char*s3=strcat(s1,s2);但是我得到了奇怪的结果.
跟前面的问题7.
1一样,这里主要的问题是没有正确地为连接结果分配空间.
C没有提供自动管理的字符串类型.
C编译器只为源码中明确提到的对象分配空间(对于字符串,这包括字符数组和串常量).
程序员必须为字符串连接这样的运行期操作的结果分配足够的空间,通常可以通过声明数组或调用malloc()完成.
strcat()不进行任何分配;第二个串原样不动地附加在第一个之后.
因此,一种解决办法是把第一个串声明为数组:chars1[20]="Hello,";35第7章内存分配36由于strcat()返回第一个参数的值,本例中为s1,s3实际上是多余的;在strcat()调用之后,s1包含结果.
提问中的strcat()调用实际上有两个问题:s1指向的字符串常数,除了空间不足以放入连接的字符串之外,甚至都不一定可写.
参见问题1.
13.
参考资料:[CT&P,Sec.
3.
2p.
32].
7.
3但是strcat的手册页说它接受两个char*型参数.
我怎么知道(空间)分配的事情呢一般地说,使用指针的时候,你必须总是考虑内存分配,除非明确知道编译器替你做了此事.
如果一个库函数的文档没有明确提到内存分配,那么通常需要调用者来考虑.
Unix型的手册页顶部的大纲段落或ANSIC标准有些误导作用.
那里展示的程序片段更像是实现者使用的函数定义而不是调用者使用的形式.
特别地,很多接受指针(如结构指针或串指针)的函数通常在调用时都用到某个由调用者分配的对象(结构,或数组——参见问题6.
3和6.
4)的指针.
其它的常见例子还有time()(参见问题13.
10)和stat().
7.
4我刚才试了这样的代码char*p;strcpy(p,"abc");而它运行正常怎么回事为什么它没有崩溃你的运气来了,我猜.
未初始化的指针p所指向的随机地址恰好对你来说是可写的,而且很显然也没有用于什么关键的数据.
参见问题11.
35.
7.
5一个指针变量分配多少内存这是个挺有误导性的问题.
当你象这样声明一个指针变量的时候,char*p;你(或者,更准确地讲,编译器)只分配了足够容纳指针本身的内存;也就是说,这种情况下,你分配了sizeof(char*)个字节的内存.
但你还没有分配任何让指针指向的内存.
参见问题7.
1和7.
2.
7.
6我有个函数,本该返回一个字符串,但当它返回调用者的时候,返回串却是垃圾信息.
确保指向的内存已经正确分配了.
例如,确保你没有做下面这样的事情:char*itoa(intn){charretbuf[20];/*错!
*/sprintf(retbuf,"%d",n);returnretbuf;/*错!
*/}第7章内存分配37一种解决方案是把返回缓冲区声明为staticcharretbuf[20];本方案并非完美,尤其是有问题的函数可能会递归调用,或者会同时使用到它的多个返回值时.
参见问题7.
7,12.
19和20.
1.
参考资料:[ISO,Sec.
6.
1.
2.
4].
7.
7那么返回字符串或其它集合的争取方法是什么呢返回指针必须是静态分配的缓冲区(如问题7.
6的答案所述),或者调用者传入的缓冲区,或者用malloc()获得的内存,但不能是局部(自动)数组.
参见问题20.
1.
7.
8为什么在调用malloc()时,我得到"警告:整数赋向指针需要类型转换"你包含了或者正确声明了malloc()吗参见问题1.
8.
参考资料:[H&S,Sec.
4.
7p.
101].
7.
9为什么有些代码小心地把malloc返回的值转换为分配的指针类型.
在ANSI/ISO标准C引入void*一般指针类型之前,这种类型转换通常用于在不兼容指针类型赋值时消除警告(或许也可能导致转换).
在ANSI/ISO标准C下,这些转换不再需要,而起事实上现代的实践也不鼓励这样做,因为它们可能掩盖malloc()声明错误时产生的重要警告;参见上面的问题7.
8.
(但是,因为这样那样的原因,为求与C++兼容,C程序中常常能见到这样的转换.
在C++中从void*的明确转换是必须的.
)参考资料:[H&S,Sec.
16.
1pp.
386-7].
7.
10在调用malloc()的时候,错误"不能把void*转换为int*"是什么意思说明你用的是C++编译器而不是C编译器.
参见问题7.
9.
7.
11我见到了这样的代码char*p=malloc(strlen(s)+1);strcpy(p,s);难道不应该是malloc((strlen(s)+1)*sizeof(char))永远也不必乘上sizeof(char),因为根据定义,sizeof(char)严格为1.
另一方面,乘上sizeof(char)也没有害处,有时候还可以帮忙为表达式引入sizet类型.
参见问题8.
5.
参考资料:[ISO,Sec.
6.
3.
3.
4];[H&S,Sec.
7.
5.
2p.
195].
第7章内存分配387.
12我如何动态分配数组参见问题6.
11和6.
13.
7.
13我听说有的操作系统程序使用的时候才真正分配malloc申请的内存.
这合法吗很难说.
标准没有说系统可以这样做,但它也没有明确说不能.
参考资料:[ISO,Sec.
7.
10.
3].
7.
14我用一行这样的代码分配一个巨大的数组,用于数字运算:double*array=malloc(300*300*sizeof(double));malloc()并没有返回null,但是程序运行得有些奇怪,好像改写了某些内存,或者malloc()并没有分配我申请的那么多内存,云云.
注意300*300是90,000,这在你乘上sizeof(double)以前就已经不能放入16位的int中了.
如果你需要分配这样大的内存空间,你可得小心为妙.
如果在你的机器上sizet(malloc()接受的类型)是32位,而int为16位,你可以写300*(300*sizeof(double))来避免这个问题.
(参见问题3.
11).
否则,你必须把你的数据结构分解为更小的块,或者使用32位的机器或编译器,或者使用某种非标准的内存分配函数.
参见问题19.
27.
7.
15我的PC有8兆内存.
为什么我只能分配640K左右的内存在PC兼容的分段结构下,很难透明地分配超过640K的内存,尤其是在MS-DOS下.
参见问题19.
27.
7.
16我的程序总是崩溃,显然在malloc内部的某个地方.
但是我看不出哪里有问题.
是malloc()有bug吗很不幸,malloc的内部数据结构很容易被破坏,而由此引发的问题会十分棘手.
最常见的问题来源是向malloc分配的区域写入比所分配的还多的数据;一个常见的bug是用malloc(strlen(s))而不是strlen(s)+1.
其它的问题还包括使用指向已经释放了的内存的指针,释放未从malloc获得的内存,或者两次释放同一个指针,或者试图重分配空指针,参见问题7.
25.
参见问题7.
23,16.
7和18.
2.
7.
17动态分配的内存一旦释放之后你就不能再使用,是吧是的.
有些早期的malloc()文档提到释放的内存中的内容会"保留",但这个欠考虑的保证并不普遍而且也不是C标准要求的.
第7章内存分配39几乎没有那个程序员会有意使用释放的内存,但是意外的使用却是常有的事.
考虑下面释放单链表的正确代码:structlist*listp,*nextp;for(listp=base;listp!
=NULL;listp=nextp){nextp=listp->next;free(listp);}请注意如果在循环表达式中没有使用临时变量nextp,而使用listp=listp->next会产生什么恶劣后果.
参考资料:[K&R2,Sec.
7.
8.
5p.
167];[ISO,Sec.
7.
10.
3];[Rationale,Sec.
4.
10.
3.
2];[H&S,Sec.
16.
2p.
387];[CT&P,Sec.
7.
10p.
95].
7.
18为什么在调用free()之后指针没有变空使用(赋值,比较)释放之后的指针有多么不安全当你调用free()的时候,传入指针指向的内存被释放,但调用函数的指针值可能保持不变,因为C的按值传参语义意味着被调函数永远不会永久改变参数的值.
参见问题4.
4.
严格的讲,被释放的指针值是无效的,对它的任何使用,即使没有解参照,也可能带来问题,尽管作为一种实现质量的表现,多数实现都不会对无伤大雅的无效指针使用产生例外.
参考资料:[ISO,Sec.
7.
10.
3];[Rationale,Sec.
3.
2.
2.
3].
7.
19当我malloc()为一个函数的局部指针分配内存时,我还需要用free()明确的释放吗是的.
记住指针和它所指向的东西是完全不同的.
局部变量在函数返回时就会释放,但是在指针变量这个问题上,这表示指针被释放,而不是它所指向的对象.
用malloc()分配的内存直到你明确释放它之前都会保留在那里.
一般地,对于每一个malloc()都必须有个对应的free()调用.
7.
20我在分配一些结构,它们包含指向其它动态分配的对象的指针.
我在释放结构的时候,还需要释放每一个下级指针吗是的.
一般地,你必须分别向free()传入malloc()返回的每一个指针,仅仅一次(如果它的确要被释放的话).
一个好的经验法则是对于程序中的每一个malloc()调用,你都可以找到一个对应的free()调用以释放malloc()分配的内存.
参见问题7.
21.
第7章内存分配407.
21我必须在程序退出之前释放分配的所有内存吗你不必这样做.
一个真正的操作系统毫无疑问会在程序退出的时候回收所有的内存和其它资源.
然而,有些个人电脑据称不能可靠地释放内存,从ANSI/ISOC的角度来看这不过是一个"实现的质量问题".
参考资料:[ISO,Sec.
7.
10.
3.
2].
7.
22我有个程序分配了大量的内存,然后又释放了.
但是从操作系统看,内存的占用率却并没有回去.
多数malloc/free的实现并不把释放的内存返回操作系统,而是留着供同一程序的后续malloc()使用.
7.
23free()怎么知道有多少字节需要释放malloc/free的实现会在分配的时候记下每一块的大小,所以在释放的时候就不必再考虑了.
7.
24那么我能否查询malloc包,可分配的最大块是多大不幸的是,没有标准的或可移植的办法.
某些编译器提供了非标准的扩展.
7.
25向realloc()的第一个参数传入空指针合法吗你为什么要这样做ANSIC批准了这种用法,以及相关的realloc(0),用于释放,尽管一些早期的实现不支持,因此可能不完全可移植.
向realloc()传入置空的指针可以更容易地写出自开始的递增分配算法.
参考资料:[ISO,Sec.
7.
10.
3.
4];[H&S,Sec.
16.
3p.
388].
7.
26calloc()和malloc()有什么区别利用calloc的零填充功能安全吗free()可以释放calloc()分配的内存吗,还是需要一个cfree()calloc(m,n)本质上等价于p=malloc(m*n);memset(p,0,m*n);填充的零是全零,因此不能确保生成有用的空指针值或浮点零值(参见第5章).
free()可以安全地用来释放calloc()分配的内存.
参考资料:[ISO,Sec.
7.
10.
3to7.
10.
3.
2];[H&S,Sec.
16.
1p.
386,Sec.
16.
2p.
386];[PCS,Sec.
11pp.
141,142].
第7章内存分配417.
27alloca()是什么为什么不提倡使用它在调用alloca()的函数返回的时候,它分配的内存会自动释放.
也就是说,用alloca分配的内存在某种程度上局部于函数的"堆栈帧"或上下文中.
alloca()不具可移植性,而且在没有传统堆栈的机器上很难实现.
当它的返回值直接传入另一个函数时会带来问题,如fgets(alloca(100),100,stdin).
由于这些原因,alloca()不合标准,不宜使用在必须广泛移植的程序中,不管它可能多么有用.
既然C99支持变长数组(VLA),它可以用来更好的完成alloca()以前的任务.
参见问题7.
19.
参考资料:[Rationale,Sec.
4.
10.
3].
第7章内存分配42第8章字符和字符串8.
1为什么strcat(string,'!
');不行字符和字符串的区别显而易见,而strcat()用于连接字符串.
C中的字符用它们的字符集值对应的小整数表示,参见下边的问题8.
4.
字符串用字符数组表示;通常你操作的是字符数组的第一个字符的指针.
二者永远不能混用.
要为一个字符串增加!
,需要使用strcat(string,"!
");参见问题1.
13,7.
2和16.
6.
参考资料:[CT&P,Sec.
1.
5pp.
9-10].
8.
2我在检查一个字符串是否跟某个值匹配.
为什么这样不行char*string;.
.
.
if(string=="value"){/*stringmatches"value"C中的字符串用字符的数组表示,而C语言从来不会把数组作为一个整体操作(赋值,比较等).
上面代码段中的==操作符比较的是两个指针——指针变量string的值和字符串常数"value"的指针值——看它们是否相等,也就是说,看它们是否指向同一个位置.
它们可能并不相等,所以比较决不会成功.
要比较两个字符串,一般使用库函数strcmp():if(strcmp(string,"value")==0){/*stringmatches"value"*/}8.
3如果我可以写chara[]="Hello,world!
";为什么我不能写chara[14];a="Hello,world!
";字符串是数组,而你不能直接用数组赋值.
可以使用strcpy()代替:strcpy(a,"Hello,world!
");参见问题1.
13,4.
1和7.
2.
43第8章字符和字符串448.
4我怎么得到对应字符的数字(字符集)值,或者相反在C语言中字符用它们的字符集值对应的小整数表示.
因此,你不需要任何转换函数:如有你有字符,你就有它的值.
数字字符和它们对应的0-9的数字之间相互转换时,加上或减去常数'0',也就是说,'0'的字符值.
参见问题13.
1和20.
8.
8.
5我认为我的编译器有问题:我注意到sizeof('a')是2而不是1(即,不是sizeof(char)).
可能有些令人吃惊,C语言中的字符常数是int型,因此sizeof('a')是sizeof(int),这是另一个与C++不同的地方.
参见问题7.
11.
参考资料:[ISO,Sec.
6.
1.
3.
4];[H&S,Sec.
2.
7.
3p.
29].
第9章布尔表达式和变量9.
1C语言中布尔值的候选类型是什么为什么它不是一个标准类型我应该用#dene或enum定义true和false值吗C语言没有提供标准的布尔类型,部分因为选一个这样的类型涉及最好由程序员决定的空间/时间折衷.
(使用int可能更快,而使用char可能更节省数据空间.
然而,如果需要和int反复转换,那么小类型也可能生成更大或更慢的代码.
)使用#dene还是枚举常数定义true/false可以随便,无关大雅(参见问题2.
16和17.
8).
使用以下任何一种形式#defineTRUE1#defineYES1#defineFALSE0#defineNO0enumbool{false,true};enumbool{no,yes};或直接使用1和0,只要在同一程序或项目中一致即可.
如果你的调试器在查看变量的时候能够显示枚举常量的名字,可能使用枚举更好.
有些人更喜欢这样的定义#defineTRUE(1==1)#defineFALSE(!
TRUE)或者定义这样的"辅助"宏#defineIstrue(e)((e)!
=0)但这样于事无益,参见下边的问题9.
2,5.
9和10.
1.
9.
2因为在C语言中所有的非零值都被看作"真",是不是把TRUE定义为1很危险如果某个内置的函数或关系操作符"返回"不是1的其它值怎么办C语言中的确任何非零值都都被看作真,但这仅限于"输入",也就是说,仅限于需要布尔值的地方.
内建操作符生成布尔值时,可以保证为1或0.
因此,这样的测试if((a==b)==TRUE)能如愿运行(只要TRUE为1),但显然这很傻.
事实上,跟TRUE和FALSE的跟TRUE和FALSE的显示比较都不合适,因为有些库函数(如isupper(),isalpha()等)在成功时返回非零值,但不一定为1.
(再说,如果你认为"if((a==b)45第9章布尔表达式和变量46==TRUE)"比"if(a==b)"好,为什么就此打住呢为什么不使用"if(((a==b)==TRUE)==TRUE)"呢)一般规则是只在向布尔变量赋值或函数参数中才使用TRUE和FALSE(或类似的东西),或者用于函数的返回值,但决不用于比较.
预处理宏TRUE和FALSE(当然还有NULL)只是用于增加代码可读性,而不是因为其值可能改变.
(参见问题5.
3和5.
8.
)尽管使用TRUE和FALSE这样的宏(或者YES和NO)看上去更清楚,布尔值和定义在C语言中的复杂性让很多程序员觉得TRUE和FALSE宏不过更令人迷惑,因而更喜欢使用1和0.
参见问题5.
7.
参考资料:[K&R1,Sec.
2.
6p.
39,Sec.
2.
7p.
41];[K&R2,Sec.
2.
6p.
42,Sec.
2.
7p.
44,Sec.
A7.
4.
7p.
204,Sec.
A7.
9p.
206];[ISO,Sec.
6.
3.
3.
3,Sec.
6.
3.
8,Sec.
6.
3.
9,Sec.
6.
3.
13,Sec.
6.
3.
14,Sec.
6.
3.
15,Sec.
6.
6.
4.
1,Sec.
6.
6.
5];[H&S,Sec.
7.
5.
4pp.
196-7,Sec.
7.
6.
4pp.
207-8,Sec.
7.
6.
5pp.
208-9,Sec.
7.
7pp.
217-8,Sec.
7.
8pp.
218-9,Sec.
8.
5pp.
238-9,Sec.
8.
6pp.
241-4];"WhattheTortoiseSaidtoAchilles".
9.
3当p是指针时,if(p)是合法的表达式吗是的.
参见问题5.
3.
第10章C预处理器10.
1这些机巧的预处理宏:#denebegin{#deneend}你觉得怎么样参见第17章.
10.
2怎么写一个一般用途的宏交换两个值对于这个问题没有什么好的答案.
如果这两个值是整数,可以使用异或的技术,但是这对浮点值或指针却不行,对同一个值也无能为力.
(参见问题3.
4和20.
14.
)如果希望这个宏用于任何类型(通常的目标),那么它不能使用临时变量,因为不知道需要什么类型的临时变量(即使知道也难以找出一个名字),而且标准C也没有提供typeof操作符.
最好的全面解决方案可能就是忘掉宏这回事,除非你还准备把类型作为第三个参数传入.
10.
3书写多语句宏的最好方法是什么通常的目标是书写一个象包含一个单独的函数调用语句的宏.
这意味着"调用者"需要提供最终的分号,而宏体则不需要.
因此宏体不能为简单的括弧包围的复合语句,因为如果这样,调用的时候就会发生语法错(明显是一个单独语句,但却多了一个额外的分号),就像在if/else语句的if分支中多了一个else分句一样.
所以,传统的结局方案就是这样使用:#defineMACRO(arg1,arg2)do{\/*declarations*/\stmt1;\stmt2;\}while(0)/*没有结尾的;*/当调用者加上分号后,宏在任何情况下都会扩展为一个单独的语句.
优化的编译器会去掉条件为0的"无效"测试或分支,尽管lint可能会警告.
如果宏体内的语句都是简单语句,没有声明或循环,那么还有一种技术,就是写一个单独的,用一个或多个逗号操作符分隔的表达式.
例如,问题10.
22的第一个DEBUG()宏.
这种技术还可以"返回"一个值.
参考资料:[H&S,Sec.
3.
3.
2p.
45];[CT&P,Sec.
6.
3pp.
82-3].
47第10章C预处理器4810.
4我第一次把一个程序分成多个源文件,我不知道该把什么放到.
c文件,把什么放到.
h文件.
(".
h"到底是什么意思)作为一般规则,你应该把这些东西放入头(.
h)文件中:宏定义(预处理#denes)结构、联合和枚举声明typedef声明外部函数声明(参见问题1.
4)全局变量声明当声明或定义需要在多个文件中共享时,尤其需要把它们放入头文件中.
特别是,永远不要把外部函数原型放到.
c文件中.
参见问题1.
3.
另一方面,如果定义或声明为一个.
c文件私有,则最好留在.
c文件中.
参见问题1.
3和10.
5.
参考资料:[K&R2,Sec.
4.
5pp.
81-2];[H&S,Sec.
9.
2.
3p.
267];[CT&P,Sec.
4.
6pp.
66-7].
10.
5一个头文件可以包含另一头文件吗这是个风格问题,因此有不少的争论.
很多人认为"嵌套包含文件"应该避免:盛名远播的"印第安山风格指南"(IndianHillStyleGuide,参见问题17.
7)对此嗤之以鼻;它让相关定义更难找到;如果一个文件被包含了两次,它会导致重复定义错误;同时他会令makele的人工维护十分困难.
另一方面,它使模块化使用头文件成为一种可能(一个头文件可以包含它所需要的一切,而不是让每个源文件都包含需要的头文件);类似grep的工具(或tags文件)使搜索定义十分容易,无论它在哪里;一种流行的头文件定义技巧是:#ifndefHFILENAME_USED#defineHFILENAME_USED.
.
.
头文件内容.
.
.
#endif每一个头文件都使用了一个独一无二的宏名.
这令头文件可自我识别,以便可以安全的多次包含;而自动Makele维护工具(无论如何,在大型项目中都是必不可少的)可以很容易的处理嵌套包含文件的依赖问题.
参见问题17.
8.
参考资料:[Rationale,Sec.
4.
1.
2].
10.
6#include和#include""有什么区别语法通常用于标准或系统提供的头文件,而""通常用于程序自己的头文件.
第10章C预处理器4910.
7完整的头文件搜索规则是怎样的准确的的行为是由实现定义的,这就是应该有文档说明;参见问题11.
32.
通常,用括起来的头文件会先在一个或多个标准位置搜索.
用""括起来的头文件会首先在"当前目录"中搜索,然后(如果没有找到)再在标准位置搜索.
参考资料:[K&R2,Sec.
A12.
4p.
231];[ISO,Sec.
6.
8.
2];[H&S,Sec.
3.
4p.
55].
10.
8我在文件的第一个声明就遇到奇怪的语法错误,但是看上去没什么问题.
可能你包含的最后一个头文件的最后一行缺一个分号.
参见问题2.
14,11.
28和16.
1.
10.
9我包含了我使用的库函数的正确头文件,可是连接器还是说它没有定义.
参见问题13.
18.
10.
10我在编译一个程序,看起来我好像缺少需要的一个或多个头文件.
谁能发给我一份根据"缺少的"头文件的种类,有几种情况.
如果缺少的头文件是标准头文件,那么你的编译器有问题.
你得向你的供货商或者精通你的编译器的人求助.
对于非标准的头文件问题更复杂一些.
有些完全是系统或编译器相关的.
某些是完全没有必要的,而且应该用它们的标准等价物代替.
例如,用代替.
其它的头文件,如跟流行的附加库相关的,可能有相当的可移植性.
标准头文件存在的部分原因就是提供适合你的编译器,操作系统和处理器的定义.
你不能从别人那里随便拷贝一份就指望它能工作,除非别人跟你使用的是同样的环境.
你可能事实上有移植性问题(参见第19章)或者编译器问题.
否则,参见问题18.
18.
10.
11我怎样构造比较字符串的#if预处理表达式你不能直接这样做;#if预处理指令只处理整数.
有一种替代的方法是定义多个整数值不一样的宏,用它们来实现条件比较.
参见问题20.
15.
参考资料:[K&R2,Sec.
4.
11.
3p.
91;ISOSec.
6.
8.
1];[H&S,Sec.
7.
11.
1p.
225].
第10章C预处理器5010.
12sizeof操作符可以用于#if预编译指令中吗不行.
预编译在编译过程的早期进行,此时尚未对类型名称进行分析.
作为替代,可以考虑使用ANSI的中定义的常量,或者使用"配置"(congure)脚本.
更好的办法是,书写与类型大小无关的代码;参见问题1.
1.
10.
13我可以在#include行里使用#ifdef来定义两个不同的东西吗不行.
你不能"让预处理器自己运行".
你能做的就是根据#ifdef设置使用两个完全不同的单独#dene行之一.
参考资料:[ISO,Sec.
6.
8.
3,Sec.
6.
8.
3.
4];[H&S,Sec.
3.
2pp.
40-1].
10.
14对typdef的类型定义有没有类似#ifdef的东西不幸的是,没有.
你必须保存一套预处理宏(如MYTYPEDEFINED)来记录某个类型是否用typdef声明了.
参见问题10.
12.
参考资料:[ISO,Sec.
5.
1.
1.
2,Sec.
6.
8.
1];[H&S,Sec.
7.
11.
1p.
225].
10.
15我如何用#if表达式来判断机器是高字节在前还是低字节在前恐怕你不能.
(预处理运算仅仅使用长整型,而且没有寻址的概念.
)你是否真的需要明确知道机器的字节顺序呢通常写出与字节顺序无关的代码更好.
参考资料:[ISO,Sec.
6.
8.
1];[H&S,Sec.
7.
11.
1p.
225].
10.
16我得到了一些代码,里边有太多的#ifdef.
我不想使用预处理器把所有的#include和#ifdef都扩展开,有什么办法只保留一种条件的代码呢有几个程序unifdef,rmifdef和scpp(selectiveCpreprocessor)正是完成这种工作的.
参见问题18.
18.
10.
17如何列出所有的预定义标识符尽管这是种常见的需求,但却没有什么标准的办法.
gcc提供了和-E一起使用的-dM选项,其它编译器也有类似的选项.
如果编译器文档没有帮助,那么可以使用类似Unix字符串工具的程序取出编译和预处理生成的可执行文件中的可打印字符串.
请注意,很多传统的系统相关的预定义标识符(如"unix")并不标准(因为和用户的名字空间冲突),因而会被删除或改名.
第10章C预处理器5110.
18我有些旧代码,试图用这样的宏来构造标识符#denePaste(a,b)a/**/b但是现在不行了.
这是有些早期预处理器实现(如Reiser)的未公开的功能,注释完全消失,因而可以用来粘结标识符.
但ANSI确认(如K&R所言)注释用空白代替.
然而对粘结标识符的需求却十分自然和广泛,因此ANSI引入了一个明确定义的标识符粘结操作符——##,它可以象这样使用#definePaste(a,b)a##b参见问题11.
18.
参考资料:[ISO,Sec.
6.
8.
3.
3];[Rationale,Sec.
3.
8.
3.
3];[H&S,Sec.
3.
3.
9p.
52].
10.
19为什么宏#deneTRACE(n)printf("TRACE:%d\n",n)报出警告"用字符串常量代替宏"它似乎应该把TRACE(count);扩展为printf("TRACE:%d\count",count);参见问题11.
19.
10.
20使用#操作符时,我在字符串常量内使用宏参数有问题.
参见问题11.
18和11.
19.
10.
21我想用预处理做某件事情,但却不知道如何下手.
C的预处理器并不是一个全能的工具.
注意,甚至都不能保证有一个单独的程序.
与其强迫它做一些不适当的事情,还不如考虑自己写一个专用的预处理工具.
你可以很容易就得到一个类似make(1)那样的工具帮助你自动运行.
如果你要处理的不是C程序,可以考虑使用一个多用途的预处理器.
在多数Unix系统上有m4工具.
10.
22怎样写参数个数可变的宏一种流行的技巧是用一个单独的用括弧括起来的的"参数"定义和调用宏,参数在宏扩展的时候成为类似printf()那样的函数的整个参数列表.
#defineDEBUG(args)(printf("DEBUG:"),printfargs)if(n!
=0)DEBUG(("nis%d\n",n));明显的缺陷是调用者必须记住使用一对额外的括弧.
gcc有一个扩展可以让函数式的宏接受可变个数的参数.
但这不是标准.
另一种可能的解决方案是根据参数个数使用多个宏(DEBUG1,DEBUG2,等等),或者用逗号玩个这样的花招:第10章C预处理器52#defineDEBUG(args)(printf("DEBUG:"),printf(args))#define_,DEBUG("i=%d"_i);C99引入了对参数个数可变的函数式宏的正式支持.
在宏"原型"的末尾加上符号.
.
.
(就像在参数可变的函数定义中),宏定义中的伪宏VAARGS就会在调用是替换成可变参数.
最后,你总是可以使用真实的函数,接受明确定义的可变参数.
参见问题15.
4和15.
5.
如果你需要替换宏,使用一个函数和一个非函数式宏,如#deneprintfmyprintf.
参考资料:[C9X,Sec.
6.
8.
3,Sec.
6.
8.
3.
1].
第11章ANSI/ISO标准C11.
1什么是"ANSIC标准"1983年,美国国家标准协会(ANSI)委任一个委员会X3J11对C语言进行标准化.
经过长期艰苦的过程,该委员会的工作于1989年12月14日正式被批准为ANSX3.
159-1989并于1990年春天颁布.
ANSIC主要标准化了现存的实践,同时增加了一些来自C++的内容(主要是函数原型)并支持多国字符集(包括备受争议的三字符序列).
ANSIC标准同时规定了C运行期库例程的标准.
一年左右以后,该标准被接受为国际标准,ISO/IEC9899:1990,这个标准甚至在美国国内(在这里它被称作ANSI/ISO9899-1990[1992])代替了早先的X3.
159.
作为一个ISO标准,它会以发行技术勘误和标准附录的形式不断更新.
1994年,技术勘误1(TC1)修正标准中40处地方,多数都是小的修改或明确,而标准附录1(NA1)增加了大约50页的新材料,多数是规定国际化支持的新库函数.
1995年,TC2增加了一些更多的小修改.
最近,该标准的一个重大修订,"C99",已经完成并被接受.
该标准的数个版本,包括C99和原始的ANSI标准,都包括了一个"基本原理"(Rational),解释它的许多决定并讨论了很多细节问题,包括本文中提及的某些内容.
11.
2我如何得到一份标准的副本可以用18美元从www.
ansi.
org联机购买一份电子副本(PDF).
在美国可以从一下地址获取印刷版本AmericanNationalStandardsInstitute11W.
42ndSt.
,13thfloorNewYork,NY10036USA(+1)2126424900和GlobalEngineeringDocuments15InvernessWayEEnglewood,CO80112USA(+1)3033972715(800)8547179(U.
S.
&Canada)其它国家,可以联系适当的国内标准组织,或Geneva的ISO,地址是:53第11章ANSI/ISO标准C54ISOSalesCasePostale56CH-1211Geneve20Switzerland或者参见URLhttp://www.
iso.
ch或查阅comp.
std.
internatFAQ列表,Stan-dards.
Faq.
由HerbertSchild注释的名不副实的《ANSIC标准注解》包含ISO9899的多数内容;这本书由Osborne/McGraw-Hill出版,ISBN为0-07-881952-0,在美国售价大约40美圆.
有人认为这本书的注解并不值它和官方标准的差价:里边错漏百出,有些标准本身的内容甚至都不全.
网上又很多人甚至建议完全忽略里边的注解.
在http://www.
lysator.
liu.
se/c/schildt.
html可以找到CliveFeather对该注解的评论("注解的注解").
"ANSI基本原理"的原本文本可以从ftp://ftp.
uu.
net/doc/standards/ansi/X3.
159-1989匿名ftp下载(参见问题18.
18),也可以在万维网从http://www.
lysator.
liu.
se/c/rat/title.
html得到.
这本基本原理由SiliconPress出版,ISBN为0-929306-07-4.
C9X的公众评论草稿可以从ISO/IECJTC1/SC22/WG14的网站得到,地址为http://www.
dkuug.
dk/JTC1/SC22/WG14/.
参见问题11.
3.
11.
3我在哪里可以找到标准的更新你可以在以下网站找到相关信息(包括C9X草案):http://www.
lysator.
liu.
se/c/index.
html,http://www.
dkuug.
dk/JTC1/SC22/WG14/和http://www.
dmk.
com/.
11.
4很多ANSI编译器在遇到以下代码时都会警告类型不匹配.
ex-ternintfunc(oat);intfunc(x)oatx;你混用了新型的原型声明"externintfunc(oat);"和老式的定义"intfunc(x)oatx;".
通常这两种风格可以混同,但是这种情况下不行.
旧的C编译器(包括未使用原型和变长参数列表的ANSIC)会"放宽"传入函数的某些参数.
浮点数被升为双精度浮点数,字符和段整型被升为整型.
对于旧式的函数定义,参数值会在被调函数的内部自动转换为对应的较窄的类型,如果在函数中那样声明了.
这个问题可以通过在定义中使用新型的语法一致性:intfunc(floatx)或者把新型原型声明改成跟旧式定义一致.
externintfunc(double);第11章ANSI/ISO标准C55这种情况下,如果可能,最好把就是定义也改成使用双精度数.
毫无疑问,在函数参数和返回值中避免使用"窄的"(char,shortint和oat)类型要安全得多.
参见问题1.
8.
参考资料:[K&R1,Sec.
A7.
1p.
186];[K&R2,Sec.
A7.
3.
2p.
202];[ISO,Sec.
6.
3.
2.
2,Sec.
6.
5.
4.
3];[Rationale,Sec.
3.
3.
2.
2,Sec.
3.
5.
4.
3];[H&S,Sec.
9.
2pp.
265-7,Sec.
9.
4pp.
272-3].
11.
5能否混用旧式的和新型的函数语法这样做是合法的,但是需要小心为妙,特别参见问题11.
4.
但是,现代的做法是在声明和定义的时候都是用原型形式.
旧式的语法被认为已经废弃,所以某一天对它的官方支持可能会取消.
参考资料:[ISO,Sec.
6.
7.
1,Sec.
6.
9.
5];[H&S,Sec.
9.
2.
2pp.
265-7,Sec.
9.
2.
5pp.
269-70].
11.
6为什么声明externintf(structx*p);报出了一个奇怪的警告信息"结构x在参数列表中声明"与C语言通常的作用范围规则大相径庭的是,在原型中第一次声明(甚至提到)的结构不能和同一源文件中的其它结构兼容,它在原型的结束出就超出了作用范围.
要解决这个问题,在同一源文件的原型之前放上这样的声明:structx;它在文件范围内提供了一个不完整的结构x的声明,这样,后续的用到结构x的声明至少能够确定它们引用的是同一个结构x.
参考资料:[ISO,Sec.
6.
1.
2.
1,Sec.
6.
1.
2.
6,Sec.
6.
5.
2.
3].
11.
7我不明白为什么我不能象这样在初始化和数组维度中使用常量:constintn=5;inta[n];const限定词真正的含义是"只读的";用它限定的对象是运行时(同常)不能被赋值的对象.
因此用const限定的对象的值并不完全是一个真正的常量.
在这点上C和C++不一样.
如果你需要真正的运行时常量,使用预定义宏#dene(或enum).
参考资料:[ISO,Sec.
6.
4];[H&S,Secs.
7.
11.
2,7.
11.
3pp.
226-7].
11.
8既然不能修改字符串常量,为什么不把它们定义为字符常量的数组一个原因是太多的代码包含第11章ANSI/ISO标准C56char*p="Hello,world!
";这样并不正确的语句.
这样的语句要受诊断信息的困扰,但真正的问题却出现在改变p所指目的的任何企图.
参见问题1.
13.
11.
9"constchar*p"和"char*constp"有何区别"constchar*p"(也可以写成"charconst*p")声明了一个指向字符常量的指针,因此不能改变它所指向的字符;"char*constp"声明一个指向(可变)字符的指针常量,就是说,你不能修改指针.
"从里到外"看就可以理解它们;参见问题1.
7.
参考资料:[ISO,Sec.
6.
5.
4.
1];[Rationale,Sec.
3.
5.
4.
1];[H&S,Sec.
4.
4.
4p.
81].
11.
10为什么我不能向接受constchar**的函数传入char**你可以向接受const-T的指针的地方传入T的指针(任何类型T都适用).
但是,这个允许在带修饰的指针类型上轻微不匹配的规则(明显的例外)却不能递归应用,而只能用于最上层.
如果你必须赋值或传递除了在最上层还有修饰符不匹配的指针,你必须明确使用类型转换(本例中,使用(constchar**)),不过,通常需要使用这样的转换意味着还有转换所不能修复的深层次问题.
参考资料:[ISO,Sec.
6.
1.
2.
6,Sec.
6.
3.
16.
1,Sec.
6.
5.
3];[H&S,Sec.
7.
9.
1pp.
221-2].
11.
11怎样正确声明main()intmain(),intmain(void)或者intmain(intargc,char*argv[])(显然argc和argv的拼写可以随便).
参见问题11.
12到11.
16.
参考资料:[ISO,Sec.
5.
1.
2.
2.
1,Sec.
G.
5.
1];[H&S,Sec.
20.
1p.
416];[CT&P,Sec.
3.
10pp.
50-51].
11.
12我能否把main()定义为void,以避免扰人的"main无返回值"警告不能.
main()必须声明为返回int,且没有参数或者接受适当类型的两个参数.
如果你调用了exit()但还是有警告信息,你可能需要插入一条冗余的return语句(或者使用某种"未到达"指令,如果有的话).
把函数声明为void并不仅仅关掉了警告信息:它可能导致与调用者(对于main(),就是C运行期初始代码)期待的不同的函数调用/返回顺序.
第11章ANSI/ISO标准C57注意,这里讨论的main()是相对于"宿体"的实现;它们不适用于"自立"的实现,因为它们可能连main()都没有.
但是,自立的实现相对比较少,如果你在使用这样的系统,你也许已经知道了.
如果你从没听说过它们之间的不同,你可能正在使用"宿体"的实现,那我们的讨论就适用.
参考资料:[ISO,5.
1.
2.
2.
1,Sec.
G.
5.
1];[H&S,Sec.
20.
1p.
416];[CT&P,Sec.
3.
10pp.
50-51].
11.
13可main()的第三个参数envp是怎么回事这是一个(尽管很常见但却)非标准的扩展.
如果你真的需要用getenv()函数提供的标准方法之外的办法读写环境变量,可能使用全局变量environ会更好(尽管它也同样并不标准).
参考资料:[ISO,Sec.
G.
5.
1];[H&S,Sec.
20.
1pp.
416-7].
11.
14我觉得把main()声明为void不会失败,因为我调用了exit()而不是return,况且我的操作系统也忽略了程序的退出/返回状态.
这跟main()函数返回与否,或者是否使用返回状态都没有关系;问题是如果main()声明得不对,它的调用者(运行期初始代码)可能甚至都不能正确调用它(因为可能产生调用习惯冲突;参见问题11.
12).
你的操作系统可能会忽略退出状态,而voidmain()在你那里也可能可行,但这不可移植而且不正确.
11.
15那么到底会出什么问题真的有什么系统不支持voidmain()吗有人报告用BC++4.
5编译的使用voidmain()的程序会崩溃.
某些编译器(包括DECCV4.
1和打开某些选项的gcc)会对voidmain()提出警告.
11.
16我一直用的那本书《熟练傻瓜C语言》总是使用voidmain().
可能这本书的作者把自己也归为目标读者的一员.
很多书不负责任地在例子中使用voidmain(),并宣称这样是正确的.
但他们错了.
11.
17从main()中,exit(status)和返回同样的status真的等价吗是也不是.
标准声称它们等价.
但是如果在退出的时候需要使用main()的局部数据,那么从main()return恐怕就不行了;参见问题16.
4.
少数非常古老不符合标准的系统可能对其中的某种形式有问题.
最后,在main()函数的递归调用时,二者显然不能等价.
第11章ANSI/ISO标准C58参考资料:[K&R2,Sec.
7.
6pp.
163-4];[ISO,Sec.
5.
1.
2.
2.
3].
11.
18我试图用ANSI"字符串化"预处理操作符#向信息中插入符号常量的值,但它字符串化的总是宏的名字而不是它的值.
你可以用下面这样的两步方法迫使宏既字符串化又扩展:#defineStr(x)#x#defineXstr(x)Str(x)#defineOPpluschar*opname=Xstr(OP);这段代码把opname置为"plus"而不是"OP".
在使用符号粘接操作符##连接两个宏的值(而不是名字)时也要采用同样的"迂回战术".
参考资料:[ISO,Sec.
6.
8.
3.
2,Sec.
6.
8.
3.
5].
11.
19警告信息"warning:macroreplacementwithinastringlit-eral"是什么意思有些ANSI前的编译器/预处理器把下面这样的宏#defineTRACE(var,fmt)printf("TRACE:var=fmt\n",var)解释为TRACE(i,%d);这样的调用会被扩展为printf("TRACE:i=%d\n",i);换言之,字符串常量内部也作了宏参数扩展.
K&R和标准C都没有定义这样的宏扩展.
当你希望把宏参数转成字符串时,你可以使用新的预处理操作符#和字符串常量连接(ANSI的另一个新功能):#defineTRACE(var,fmt)\printf("TRACE:"#var"="#fmt"\n",var)参见问题11.
18.
参考资料:[H&S,Sec.
3.
3.
8p.
51].
11.
20在我用#ifdef去掉的代码里出现了奇怪的语法错误.
在ANSIC中,被#if,#ifdef或#ifndef"关掉"的代码仍然必须包含"合法的预处理符号".
这意味着字符"和'必须像在真正的C代码中那样严格配对,且这样的配对不能跨行.
特别要注意缩略语中的撇号看起来很像字符常量的开始.
因此,自然语言的注释和伪代码必须写在"正式的"注释分界符/*和*/中.
但是请参见问题20.
18和10.
21.
参考资料:[ISO,Sec.
5.
1.
1.
2,Sec.
6.
1];[H&S,Sec.
3.
2p.
40].
第11章ANSI/ISO标准C5911.
21#pragma是什么,有什么用#pragam指令提供了一种单一的明确定义的"救生舱",可以用作各种(不可移植的)实现相关的控制和扩展:源码表控制、结构压缩、警告去除(就像lint的老/*NOTREACHED*/注释),等等.
参考资料:[ISO,Sec.
6.
8.
6];[H&S,Sec.
3.
7p.
61].
11.
22"#pragmaonce"是什么意思我在一些头文件中看到了它.
这是某些预处理器实现的扩展用于使头文件自我识别;它跟问题10.
5中讲到的#ifndef技巧等价,不过移植性差些.
11.
23a[3]="abc";合法吗它是什么意思尽管只在极其有限的环境下有用,可它在ANSIC(可能也包括一些ANSI之前的系统)中是合法的.
它声明了一个长度为3的数组,把它的三个字符初始化为'a','b'和'c',但却没有通常的'\0'字符.
因此该数组并不是一个真正的C字符串从而不能用在strcpy,printf%s等当中.
多数时候,你应该让编译器计算数组初始化的初始值个数,在初始值"abc"中,计算得长度当然应该是4.
参考资料:[ISO,Sec.
6.
5.
7];[H&S,Sec.
4.
6.
4p.
98].
11.
24为什么我不能对void*指针进行运算编译器不知道所指对象的大小.
在作运算之前,把指针转化为char*或你准备操作的其它指针类型,但是请参考问题4.
3.
参考资料:[ISO,Sec.
6.
1.
2.
5,Sec.
6.
3.
6];[H&S,Sec.
7.
6.
2p.
204].
11.
25memcpy()和memmove()有什么区别如果源和目的参数有重叠,memmove()提供有保证的行为.
而memcpy()则不能提供这样的保证,因此可以实现得更加有效率.
如果有疑问,最好使用memmove().
参考资料:[K&R2,Sec.
B3p.
250];[ISO,Sec.
7.
11.
2.
1,Sec.
7.
11.
2.
2];[Rationale,Sec.
4.
11.
2];[H&S,Sec.
14.
3pp.
341-2];[PCS,Sec.
11pp.
165-6].
11.
26malloc(0)有什么用返回一个控指针还是指向0字节的指针ANSI/ISO标准声称它可能返回任意一种;其行为由实现定义.
参考资料:[ISO,Sec.
7.
10.
3];[PCS,Sec.
16.
1p.
386].
第11章ANSI/ISO标准C6011.
27为什么ANSI标准规定了外部标示符的长度和大小写限制问题在于连接器既不受ANSI/ISO标准的控制也不遵守C编译器开发者的规定.
限制仅限于标识符开始的几个字符而不是整个标识符.
在原来的ANSI标准中限制为6个字符,但在C99中放宽到了31个字符.
参考资料:[ISO,Sec.
6.
1.
2,Sec.
6.
9.
1];[Rationale,Sec.
3.
1.
2];[C9X,Sec.
6.
1.
2];[H&S,Sec.
2.
5pp.
22-3].
11.
28我的编译对最简单的测试程序报出了一大堆的语法错误.
可能是个ANSI前的编译器,不能接受函数原型或类似的东西.
参见问题1.
11,10.
8,11.
29和16.
1.
11.
29为什么有些ASNI/ISO标准库函数未定义我明明使用的就是ANSI编译器.
你很可能有一个接受ANSI语法的编译器,但并没有安装兼容ANSI的头文件或运行库.
事实上,这种情形在使用非供货商提供的编译器,如gcc时非常常见.
参见问题11.
28,12.
22和13.
19.
11.
30谁有把旧的C程序转化为ANSIC或相反的工具,或者自动生成原型的工具有两个程序protoize和unprotoize可以在有原型和无原型的函数定义和声明之间相互转换.
这些程序不能完全完成"经典"C和ANSIC之间的转换.
这些程序是FSF的GNUC编译器发布的一部分;参加问题18.
3.
GNUGhostScript包提供了一个叫ansi2knr的程序.
从ANSIC向旧式代码转化之前,请注意这样的转化不能总是正确和自动.
ANSIC引入了K&RC没有提供的诸多新功能和复杂性.
你得特别小心有原型的函数调用;也可能需要插入明确的类型转换.
参加问题11.
4和11.
28.
存在几个类型生成器,其中多数都是对lint的修改.
1992年3月在comp.
sources.
misc上发布了一个叫做CPROTO的程序.
还有一个叫做"cextract"的程序.
很多供货商都会随他们的编译器提供类似的小工具.
参见问题18.
18.
但在为"窄"参数的旧函数生成原型可要小心;参加问题11.
4.
11.
31为什么声称兼容ANSI的FrobozzMagicC编译器不能编译这些代码我知道这些代码是ANSI的,因为gcc可以编译.
许多编译器都支持一些非标准的扩展,gcc尤甚.
你能确认被拒绝的代码不依赖这样的扩展吗通常用试验特定的编译器来确定一种语言的特性是个坏主意;使用的标准可能允许变化,而编译器也可能有错.
参见问题11.
35.
第11章ANSI/ISO标准C6111.
32人们好像有些在意实现定义(implementation-den-ed)、未明确(unspecied)和无定义(undened)行为的区别.
它们的区别到底在哪里简单地说:实现定义意味着实现必须选择某种行为并提供文档.
未明确意味着实现必须选择某种行为但不必提供文档.
未定义意味着任何事情都可能发生.
标准在任何情况下都不强加需求;前两种情况下,它有时建议一组可能的行为(也可能要求从中选择一种).
注意既然标准对无定义行为没有强制要求,编译器就绝对可以做任何事情.
特别地,对程序其它部分的正常运行没有任何保证;参见问题3.
2,有一个相对简单的例子.
如果你对书写可移植代码有兴趣,你可以忽略它们的区别,因为通常你都希望避免依赖三种行为中的任何一种.
参见问题3.
8和11.
34.
第四种不那么严格定义的行为是"场景特定"(locale-specic).
参考资料:[ISO,Sec.
3.
10,Sec.
3.
16,Sec.
3.
17];[Rationale,Sec.
1.
6].
11.
33一个程序的"合法","有效"或"符合"到底是什么意思简单地说,标准谈到了三种符合:符合程序、严格符合程序和符合实现.
"符合程序"是可以由符合实现接受的程序.
"严格符合程序"是完全按照标准规定使用语言,不依赖任何实现定义、未确定或未定义功能的程序.
"符合实现"是按标准声称的实现的程序.
参考资料:[ISO,Sec.
];[Rationale,Sec.
1.
7].
11.
34我很吃惊,ANSI标准竟然有那么多没有定义的东西.
标准的唯一任务不就是让这些东西标准化吗某些构造随编译器和硬件的实现而变化,这一直是C语言的一个特点.
这种有意的不严格规定可以让编译器生成效率更高的代码,而不必让所有程序为了不合理的情况承担额外的负担.
因此,标准只是把现存的实践整理成文.
编程语言标准可以看作是语言使用者和编译器实现者之间的协议.
协议的一部分是编译器实现者同意提供,用户可以使用的功能.
而其它部分则包括用户同意遵守,编译器实现者认为会被最受的规则.
只要双方都恪守自己的保证,程序就可以正确运行.
如果任何一方违背它的诺言,则结果肯定失败.
参见问题11.
35.
参考资料:[Rationale,Sec.
1.
1].
第11章ANSI/ISO标准C6211.
35有人说i=i++的行为是未定义的,但是我刚在一个兼容ANSI的编译器上测试,得到了我希望的结果.
面对未定义行为的时候,包括范围内的实现定义行为和未确定行为,编译器可以做任何实现,其中也包括你所有期望的结果.
但是依靠这个实现却不明智.
参加问题7.
4,11.
31,11.
32和11.
34.
第12章标准输入输出库12.
1这样的代码有什么问题charc;while((c=getchar())!
=EOF).
.
.
第一,保存getchar的返回值的变量必须是int型.
getchar()可能返回任何字符值,包括EOF.
如果把getchar的返回值截为char型,则正常的字符可能会被错误的解释为EOF,或者EOF可能会被修改(尤其是char型为无符号的时候),从而永不出现.
参考资料:[K&R1,Sec.
1.
5p.
14];[K&R2,Sec.
1.
5.
1p.
16];[ISO,Sec.
6.
1.
2.
5,Sec.
7.
9.
1,Sec.
7.
9.
7.
5];[H&S,Sec.
5.
1.
3p.
116,Sec.
15.
1,Sec.
15.
6];[CT&P,Sec.
5.
1p.
70];[PCS,Sec.
11p.
157].
12.
2我有个读取直到EOF的简单程序,但是我如何才能在键盘上输入那个"EOF"呢其实,你的C程序看到的EOF的值和你用键盘发出的文件结束按键组合之间没有任何直接联系.
根据你的操作系统,你可能使用不同的按键组合来表示文件结束,通常是Control-D或Control-Z.
12.
3为什么这些代码while(!
feof(infp)){fgets(buf,MAXLINE,infp);fputs(buf,outfp);}把最后一行复制了两遍在C语言中,只有输入例程试图读并失败以后才能得到文件结束符.
换言之,C的I/O和Pascal的不一样.
通常你只需要检查输入例程的返回值,例如,fgets()在遇到文件结束符的时候返回NULL.
实际上,在任何情况下,都完全没有必要使用feof().
参考资料:[K&R2,Sec.
7.
6p.
164];[ISO,Sec.
7.
9.
3,Sec.
7.
9.
7.
1,Sec.
7.
9.
10.
2];[H&S,Sec.
15.
14p.
382].
12.
4我的程序的屏幕提示和中间输出有时显示在屏幕上,尤其是当我用管道向另一个程序输出的时候.
在输出需要显示的时候最好使用明确的ush(stdout)调用,尤其是当显示的63第12章标准输入输出库64文本没有\n结束符时.
有几种机制会努力帮助你在"适当的时机"执行ush,但这仅限于stdout为交互终端的时候.
参见问题12.
21.
参考资料:[ISO,Sec.
7.
9.
5.
2].
12.
5我怎样不等待回车键一次输入一个字符参见问题19.
1.
12.
6我如何在printf的格式串中输出一个'%'我试过\%,但是不行.
只需要重复百分号:%%.
\%不行,因为反斜杠\是编译器的转义字符,而这里我们的问题最终是printf的转义字符.
参见问题19.
21.
参考资料:[K&R1,Sec.
7.
3p.
147];[K&R2,Sec.
7.
2p.
154];[ISO,Sec.
7.
9.
6.
1].
12.
7有人告诉我在printf中使用%lf不正确.
那么,如果scanf()需要%lf,怎么可以用在printf()中用%f输出双精度数呢printf的%f标识符的确既可以输出浮点数又可以输出双精度数.
根据"缺省参数扩展"规则,不论范围内有没有原形都会在在类似printf的可变长度参数列表中采用,浮点型的变量或扩展为双精度型,因此printf()只会看到双精度数.
printf()的确接受%Lf,用于输出长双精度数.
参见问题12.
11和15.
2.
参考资料:[K&R1,Sec.
7.
3pp.
145-47,Sec.
7.
4pp.
147-50];[K&R2,Sec.
7.
2pp.
153-44,Sec.
7.
4pp.
157-59];[ISO,Sec.
7.
9.
6.
1,Sec.
7.
9.
6.
2];[H&S,Sec.
15.
8pp.
357-64,Sec.
15.
11pp.
366-78];[CT&P,Sec.
A.
1pp.
121-33].
12.
8对于sizet那样的类型定义,当我不知道它到底是long还是其它类型的时候,我应该使用什么样的printf格式呢把那个值转换为一个已知的长度够大的类型,然后使用与之对应的printf格式.
例如,输出某种类型的长度,你可以使用printf("%lu",(unsignedlong)sizeof(thetype));12.
9我如何用printf实现可变的域宽度就是说,我想在运行时确定宽度而不是使用%8dprintf("%*d",width,x)就能达到你的要求.
参见问题12.
14.
第12章标准输入输出库65参考资料:[K&R1,Sec.
7.
3];[K&R2,Sec.
7.
2];[ISO,Sec.
7.
9.
6.
1];[H&S,Sec.
15.
11.
6];[CT&P,Sec.
A.
1].
12.
10如何输出在千位上用逗号隔开的数字金额数字呢提供了一些函数可以完成这些操作,但是没有完成这些任务的标准方法.
printf()惟一一处对应locale的地方就是改变它的小数点字符.
参考资料:[ISO,Sec.
7.
4];[H&S,Sec.
11.
6pp.
301-4].
12.
11为什么scanf("%d",i)调用不行传给scanf()的参数必须是指针.
改为scanf("%d",&i)即可修正上面的问题.
12.
12为什么chars[30];scanf("%s",s);不用&也可以需要指针;并不表示一定需要&操作符.
当你向scanf()传入一个指针的时候,你不需要使用&,因为不论是否带&操作符,数组总是以指针形式传入函数的.
参见问题6.
3和6.
4.
12.
13为什么这些代码doubled;scanf("%f",&d);不行跟printf()不同,scanf()用%lf代表双精度数,用%f代表浮点数.
参见问题12.
7.
12.
14怎样在scanf()格式串中指定可变的宽度不能:scanf()格式串中的星号表示禁止赋值.
你可以使用ANSI的字符串化和字符连接完成同样的事情,或者你可以在运行时创建scanf格式串.
12.
15当我用"%d\n"调用scanf从键盘读取数字的时候,好像要多输入一行函数才返回.
可能令人吃惊,\n在scanf格式串中不表示等待换行符,而是读取并放弃所有的空白字符.
参见问题12.
18.
参考资料:[K&R2,Sec.
B1.
3pp.
245-6];[ISO,Sec.
7.
9.
6.
2];[H&S,Sec.
15.
8pp.
357-64].
第12章标准输入输出库6612.
16我用scanf%d读取一个数字,然后再用gets()读取字符串,但是编译器好像跳过了gets()调用!
scanf%d不处理结尾的换行符.
如果输入的数字后边紧接着一个换行符,则换行符会被gets()处理.
作为一个一般规则,你不能混用scanf()和gets(),或任何其它的输入例程的调用;scanf对换行符的特殊处理几乎一定会带来问题.
要么就用scanf()处理所有的输入,要么干脆不用.
参见问题12.
18和12.
20.
参考资料:[ISO,Sec.
7.
9.
6.
2];[H&S,Sec.
15.
8pp.
357-64].
12.
17我发现如果坚持检查返回值以确保用户输入的是我期待的数值,则scanf()的使用会安全很多,但有的时候好像会陷入无限循环.
在scanf()转换数字的时候,它遇到的任何非数字字符都会终止转换并被保留在输入流中.
因此,除非采用了其它的步骤,那么未预料到的非数字输入会不断"阻塞"scanf():scanf()永远都不能越过错误的非数字字符而处理后边的合法数字字符.
如果用户在数字格式的scanf如%d或%f中输入字符'x',那么提示后并用同样的scanf()调用重试的代码会立即遇到同一个'x'.
参见问题12.
18.
参考资料:[ISO,Sec.
7.
9.
6.
2];[H&S,Sec.
15.
8pp.
357-64].
12.
18为什么大家都说不要使用scanf()那我该用什么来代替呢scanf()有很多问题——参见问题12.
15,12.
16和12.
17.
而且,它的%s格式有着和gets()一样的问题(参见问题12.
20)——很难保证接收缓冲不溢出.
更一般地讲,scanf()的设计使用于相对结构化的,格式整齐的输入.
设计上,它的名称就是来自于"scanformatted".
如果你注意到,它会告诉你成功或失败,但它只能提供失败的大略位置,至于失败的原因,就无从得知了.
对scanf()多得体的错误恢复几乎是不可能的;通常先用类似fgets()的函数读入整行,然后再用sscanf()或其它技术解释.
strtol(),strtok()和atoi()等函数通常有用;参见问题13.
4.
如果你真的要用任何scanf的变体,你要确保检查返回值,以确定找到了期待的值.
而使用%s格式的时候,一定要小心缓冲区溢出.
参考资料:[K&R2,Sec.
7.
4p.
159].
12.
19我怎样才知道对于任意的sprintf调用需要多大的目标缓冲区怎样才能避免sprintf()目标缓冲区溢出当用于sprintf()的格式串已知且相对简单时,你有时可以预测出缓冲区的大小.
如果格式串中包含一个或两个%s,你可以数出固定字符的个数再加上对插入的字符串的strlen()调用的返回值.
对于整形,%d输出的字符数不会超过第12章标准输入输出库67((sizeof(int)*CHAR_BIT+2)/3+1)/*+1for'-'*/CHARBIT在中定义,但是这个计算可能有些过于保守了.
它计算的是数字以八进制存储需要的字节数;十进制的存储可以保证使用同样或更少的字节数.
当格式串更复杂或者在运行前未知的时候,预测缓冲区大小会变得跟重新实现sprintf一样困难,而且会很容易出错.
有一种最后防线的技术,就是fprintf()向一块内存区或临时文件输出同样的内容,然后检查fprintf的返回值或临时文件的大小,但请参见问题19.
14,并提防写文件错误.
如果不能确保缓冲区足够大,你就不能调用sprintf(),以防缓冲区溢出后改写其它的内存区.
如果格式串已知,你可以用%.
Ns控制%s扩展的长度,或者使用%.
*s,参见问题12.
9.
要避免溢出问题,你可以使用限制长度的sprintf()版本,即snprintf().
这样使用:snprintf(buf,bufsize,"Youtyped\"%s\"",answer);snprintf()在几个stdio库中已经提供好几年了,包括GNU和4.
4bsd.
在C99中已经被标准化了.
作为一个额外的好处,C99的snprintf()提供了预测任意sprintf()调用所需的缓冲区大小的方法.
C99的snprintf()返回它可能放到缓冲区的字符数,而它又可以用0作为缓冲区大小进行调用.
因此nch=snprintf(NULL,0,fmtstring,/*其它参数*/);这样的调用就可以预测出格式串扩展后所需要的字符数.
另一个(非标准的)选择是asprintf()函数,在bsd和GNU的C库中都有提供,它调用malloc为格式串分配空间,并返回分配内存区的指针.
这样使用:char*buf;asprintf(&buf,"%d=%s",42,"forty-two");/*现在,buf指向含有格式串的malloc的内存*/参考资料:[C9X,Sec.
7.
13.
6.
6].
12.
20为什么大家都说不要使用gets()跟fgets()不同,gets()不能被告知输入缓冲区的大小,因此不能避免缓冲区的溢出.
标准库的fgets()函数对gets()作了很大的改进,尽管它仍然不完善.
如果真的可能输入很长的行,还是需要仔细思考,正确处理.
参见问题7.
1用fgets()代替gets()的代码片断.
参考资料:[Rationale,Sec.
4.
9.
7.
2];[H&S,Sec.
15.
7p.
356].
12.
21为什么调用printf()之后errno内有ENOTTY如果stdout是终端,很多stdio包都会对其行为进行细微的调整.
为了做出判断,这些实现会执行某些当stdout为终端时会失败的操作.
尽管输出操作成功完第12章标准输入输出库68成,errno还是会被置为ENOTTY.
注意,只有当函数报告错误之后检查errno的内容才有意义.
errno在其它情况下也不保证为0.
参考资料:[ISO,Sec.
7.
1.
4,Sec.
7.
9.
10.
3];[CT&P,Sec.
5.
4p.
73];[PCS,Sec.
14p.
254].
12.
22fgetops/fsetops和ftell/fseek之间有什么区别fgetops()和fsetops()到底有什么用处ftell()和fseek()用长整型表示文件内的偏移(位置),因此,偏移量被限制在20亿(2311)以内.
而新的fgetpos()和fsetpos()函数使用了一个特殊的类型定义fpost来表示偏移量.
这个类型会适当选择,因此,fgetpos()和fsetpos可以表示任意大小的文件偏移.
fgetpos()和gsetpos()也可以用来记录多字节流式文件的状态.
参见问题1.
2.
参考资料:[K&R2,Sec.
B1.
6p.
248];[ISO,Sec.
7.
9.
1,Secs.
7.
9.
9.
1,7.
9.
9.
3];[H&S,Sec.
15.
5p.
252].
12.
23如何清除多余的输入,以防止在下一个提示符下读入ush(stdin)可以吗ush()仅对输出流有效.
因为它对"ush"的定义是用于完成缓冲字符的写入,而对于输入流ush并不是用于放弃剩余的输入.
参考资料:[ISO,Sec.
7.
9.
5.
2];[H&S,Sec.
15.
2].
12.
24既然ush()不能,那么怎样才能清除输入呢这取决于你要做什么.
如果你希望丢掉调用scanf()(参见问题12.
16-12.
17)之后所剩下的换行符和未预知的输入,你可能需要重写你的scanf()或者换掉它,参见问题12.
18.
或者你可以用下边这样的代码吃掉一行中多余的字符while((c=getchar(n'&&c!
=EOF)/*丢弃*/;你也可以使用curses的ushinp()函数.
没有什么标准的办法可以丢弃标准输入流的未读取字符,即使有,那也不够,因为未读取字符也可能来自其它的操作系统级的输入缓冲区.
如果你希望严格丢弃多输入的字符(可能是预测发出临界提示),你可能需要使用系统相关的技术;参加问题19.
1和19.
2.
参考资料:[ISO,Sec.
7.
9.
5.
2];[H&S,Sec.
15.
2].
12.
25对某些路径文件名调用fopen()总是失败.
参见问题19.
21和19.
22.
第12章标准输入输出库6912.
26我想用"r+"打开一个文件,读出一个字符串,修改之后再写入,从而就地更新一个文件.
可是这样不行.
确保在写操作之前先调用fseek,回到你准备覆盖的字串的开始,况且在读写"+"模式下的读和写操作之间总是需要fseek或ush.
同时,记住改写同样数量的字符,而且在文本模式下改写可能会在改写处把文件长度截断,因而你可能需要保存行长度.
参见问题19.
17.
参考资料:[ISO,Sec.
7.
9.
5.
3].
12.
27怎样在程序里把stdin或stdout重定向到文件使用freopen(),但请参见下边的问题12.
28.
12.
28一旦使用freopen()之后,怎样才能恢复原来的stdout(或stdin)没有什么好办法.
如果你需要恢复回去,那么最好一开始就不要使用fre-open().
可以使用你自己的可以随意赋值的输出(输入)流变量,而不要去动原来的输出(或输入)流.
有一种不可移植的办法,可以在调用freopen()之前保存流的信息,以便其后恢复原来的流.
一种办法是使用系统相关的调用如dup(),dup2()等.
另一种办法是复制或查看FILE结构的内容,但是这种方法完全没有可移植性而且很不可靠.
12.
29怎样同时向两个地方输出,如同时输出到屏幕和文件直接做不到这点.
但是你可以写出你自己的printf变体,把所有的内容都输出两次.
下边有个简单的例子:#include#includevoidf2printf(FILE*fp1,FILE*fp2,char*fmt,.
.
.
){va_listargp;va_start(argp,fmt);vfprintf(fp1,fmt,argp);va_end(argp);va_start(argp,fmt);vfprintf(fp2,fmt,argp);va_end(argp);}这里的f2printf()就跟fprintf()一样,除了它接受两个文件指针并同时输出到两个文件.
参见问题15.
5.
第12章标准输入输出库7012.
30怎样正确的读取二进制文件我有时看到0x0a和0x0d混淆了,而且如果数据中包含0x1a的话,我好像会提前遇到EOF.
读取二进制数据文件的时候你应该用"rb"调用fopen(),确保不会发生文本文件的解释.
类似的,写二进制文件时,使用"wb".
注意文本/二进制区别只是发生在文件打开时:一旦文件打开之后,在其上调用何种I/O函数无关紧要.
参考资料:[ISO,Sec.
7.
9.
5.
3];[H&S,Sec.
15.
2.
1p.
348].
第13章库函数13.
1怎样把数字转为字符串(与atoi相反)有itoa()函数吗用sprintf()就可以了.
不需担心用sprintf()会小题大作,也不必担心会浪费运行时间或代码空间;实践中它工作得挺好.
参见问题7.
6答案中的实例;以及问题8.
4和12.
19.
你也可以用sprintf()把长整形或浮点数转换成字符串(使用%ld或%f).
参考资料:[K&R1,Sec.
3.
6p.
60];[K&R2,Sec.
3.
6p.
64].
13.
2为什么strncpy()不能总在目标串放上终止符'\0'strncpy()最初被设计为用来处理一种现在已经废弃的数据结构——定长,不必'\0'结束的"字符串".
strncpy的另一个怪癖是它会用多个'\0'填充短串,直到达到指定的长度.
在其它环境中使用strncpy()有些麻烦,因为你必须经常在目的串末尾手工加'\0'.
你可以用strncat代替strncpy来绕开这个问题:如果目的串开始时为空(就是说,如果你先用*dest='\0'),strncat()就可以完成你希望strncpy()完成的事情.
另外一个方法是用sprintf(dest,"%.
*s",n,source).
如果需要复制任意字节(而不是字符串),memcpy()是个比strncpy()更好的选择.
13.
3为什么有些版本的toupper()对大写字符会有奇怪的反应为什么有的代码在调用toupper()前先调用tolower()老版的toupper()和tolower()不一定能够正常处理不需要转换的字符参数,例如数字、标点或已经符合请求的字符.
在ANSI/ISO标准C中,这些函数保证对所有的字符参数正常处理.
参考资料:[ISO,Sec.
7.
3.
2];[H&S,Sec.
12.
9pp.
320-1];[PCS,p.
182].
71第13章库函数7213.
4怎样把字符串分隔成用空白作间隔符的段怎样实现类似传递给main()的argc和argv标准中唯一用于这种分隔的函数是strtok(),虽然用起来需要些技巧,而且不一定能做到你所要求的所有事.
例如,它不能处理引用.
参考资料:[K&R2,Sec.
B3p.
250];[ISO,Sec.
7.
11.
5.
8];[H&S,Sec.
13.
7pp.
333-4];[PCS,p.
178].
13.
5我需要一些处理正则表达式或通配符匹配的代码.
确保你知道经典的正则表达式和文件名通配符的不同.
前者的变体在Unix工具ed和grep等中使用,后者的变体在多数操作系统中使用.
有许多匹配正则表达式的包可以利用.
很多包都是用成对的函数,一个"编译"正则表达式,另一个"执行"它,即用它比较字符串.
查查头文件或和函数regcmp/regex,regcomp/regexec,或recomp/reexec.
这些函数可能在一个单独的regexp库中.
在ftp://ftp.
cs.
toronto.
edu/pub/regexp.
shar.
Z或其它地方可以找到一个HenrySpencer开发的广受欢迎的regexp包,这个包也可自由再发布.
GNU工程有一个叫做rx的包.
参见问题18.
18.
文件名通配符匹配(有时称之为"globbing")在不同的系统上有不同的实现.
在Unix上,shell会在进程调用之前自动扩展通配符,因此,程序几乎从不需要专门考虑它们.
在MS-DOS下的编译器中,通常都可以在建立argv的时候连接一个用来扩展通配符的特殊目标文件.
有些系统(包括MS-DOS和VMS)会提供通配符指定文件的列表和打开的系统服务.
参见问题19.
25和20.
2.
13.
6我想用strcmp()作为比较函数,调用qsort()对一个字符串数组排序,但是不行.
你说的"字符串数组"实际上是"字符指针数组".
qsort比较函数的参数是被排序对象的指针,在这里,也就是字符指针的指针.
然而strcmp()只接受字符指针.
因此,不能直接使用strcmp().
写一个下边这样的间接比较函数:/*通过指针比较字符串*/intpstrcmp(constvoid*p1,constvoid*p2){returnstrcmp(*(char*const*)p1,*(char*const*)p2);}比较函数的参数表示为"一般指针"constvoid*.
然后,它们被转换回本来表示的类型(指向字符指针的指针),再复引用,生成可以传入strcmp()的char*.
不要被[K&R2]5.
11节119-20页的讨论所误导,那里讨论的不是标准库中的qsort.
参考资料:[ISO,Sec.
7.
10.
5.
2];[H&S,Sec.
20.
5p.
419].
第13章库函数7313.
7我想用qsort()对一个结构数组排序.
我的比较函数接受结构指针,但是编译器认为这个函数对于qsort()是错误类型.
我要怎样转换这个函数指针才能避免这样的警告这个转换必须在比较函数中进行,而函数必须定义为接受"一般指针"(constvoid*)的类型,就象上文问题13.
6中所讨论的.
比较函数可能像这样:intmystructcmp(constvoid*p1,constvoid*p2){conststructmystruct*sp1=p1;conststructmystruct*sp2=p2;/*现在比较sp1->whatever和sp2-从一般指针到结构mystruct指针的转换过程发生在sp1=p1和sp2=p2的初始化中;由于p1和p2都是void指针,编译器隐式的进行了类型转换.
另一方面,如果你对结构的指针进行排序,则如问题13.
6所示,你需要间接使用:sp1=*(structmystruct*const*)p1.
一般而言,为了让编译器"闭嘴"而进行类型转换是一个坏主意.
编译器的警告信息通常希望告诉你某些事情,忽略或轻易去掉会让你陷入危险,除非你明确知道你在做什么.
参见问题4.
5.
参考资料:[ISO,Sec.
7.
10.
5.
2];[H&S,Sec.
20.
5p.
419].
13.
8怎样对一个链表排序有时侯,有时侯,在建立链表时就一直保持链表的顺序要简单些(或者用树代替).
插入排序和归并排序算法用在链表最合适了.
如果你希望用标准库函数,你可以分配一个暂时的指针数组,填入链表中所有节点的地址,再调用qsort(),最后依据排序后的数组重新建立链表.
参考资料:[Knuth,Sec.
5.
2.
1pp.
80-102,Sec.
5.
2.
4pp.
159-168];[Sedgewick,Sec.
8pp.
98-100,Sec.
12pp.
163-175].
13.
9怎样对多于内存的数据排序你可以用"外部排序"法,[Knuth]第三卷中有详情.
基本的思想是对数据分段进行排序,每次的大小尽可能多的填入内存中,把排好序的数据段存入暂时文件中,再归并它们.
如果你的操作系统提供一个通用排序工具,你可以从程序中调用:参见问题19.
30和19.
31.
参考资料:[Knuth,Sec.
5.
4pp.
247-378];[Sedgewick,Sec.
13pp.
177-187].
13.
10怎样在C程序中取得当前日期或时间只要使用函数time(),ctime(),localtime()和/或strftime()就可以了.
下面是个简单的例子:第13章库函数74#include#includeintmain(){time_tnow;time(&now);printf("It's%s",ctime(&now));return0;}用函数strftime()可以控制输出的格式.
如果你需要小于秒的解析度,参见问题19.
36.
参考资料:[K&R2,Sec.
B10pp.
255-7];[ISO,Sec.
7.
12];[H&S,Sec.
18].
13.
11我知道库函数localtime()可以把timet转换成结构structtm,而ctime()可以把timet转换成为可打印的字符串.
怎样才能进行反向操作,把structtm或一个字符串转换成timetANSIC提供了库函数mktime(),它把structtm转换成timet.
把一个字符串转换成timet比较难些,这是由于可能遇到各种各样的日期和时间格式.
某些系统提供函数strptime(),基本上是strftime()的反向函数.
其它常用的函数有partime()(与RCS包一起被广泛的发布)和getdate()(还有少数其它函数,发布在C的新闻组).
参见问题18.
18.
参考资料:[K&R2,Sec.
B10p.
256];[ISO,Sec.
7.
12.
2.
3];[H&S,Sec.
18.
4pp.
401-2].
13.
12怎样在日期上加N天怎样取得两个日期的时间间隔ANSI/ISO标准C函数mktime()和ditime()对这两个问题提供了一些有限的支持.
mktime()接受没有规格化的日期,所以可以用一个日期的structtm结构,直接在tmmday域进行加或减,然后调用mktime()对年、月、日域进行规格化,同时也转换成了timet值.
可以用mktime()来计算两个日期的timet值,然后用ditime()计算两个timet值的秒数差分.
但是,这些方法只有日期在timet表达范围内才保证工作正常.
对于保守的timet,通常范围是从1970年到大约2037年;注意有些timet的表达不是按照Unix和Posix标准的.
tmmday域是个int,所以日偏移量超出32,736就会上溢.
还要注意,在夏令时转换的时候,一天并不是24小时,所以不要假设可以用86400整除.
另一个解决的方法是用"Julian日期",这可以支持更宽的时间范围.
处理Julian日期的代码可以在以下地方找到:Snippets收集(参见问题18.
16);Simtel/Oakland站点(文件JULCAL10.
ZIP,参见问题18.
18)和文献中提到的文章"Dateconversions"[Burki].
第13章库函数75参见问题13.
11,20.
27和20.
28.
参考资料:[K&R2,Sec.
B10p.
256];[ISO,Secs.
7.
12.
2.
2,7.
12.
2.
3];[H&S,Secs.
18.
4,18.
5pp.
401-2];[Burki].
13.
13我需要一个随机数生成器.
标准C库函数就有一个:rand().
你系统上的实现可能并不完美,但写一个更好的并不是一件容易的事.
如果你需要实现自己的随机数生成器,有许多这方面的文章可供参考;象下面的文献或sci.
math.
num-analysis的FAQ.
网上也有许多这方面的包:老的可靠的包有r250,RANLIB和FSULTRA(参见问题18.
18),还有由Marsaglia,Matumoto和Nishimura新近的成果"MersenneTwister",另外就是DonKnuth个人网页上收集的代码.
参考资料:[K&R2,Sec.
2.
7p.
46,Sec.
7.
8.
7p.
168];[ISO,Sec.
7.
10.
2.
1];[H&S,Sec.
17.
7p.
393];[PCS,Sec.
11p.
172];[Knuth,Vol.
2Chap.
3pp.
1-177];[P&M].
13.
14怎样获得在一定范围内的随机数直接的方法是rand()%N/*不好*/试图返回从0到N1的数字.
但这个方法不好,因为许多随机数发生器的低位比特并不随机,参见问题13.
16.
一个较好的方法是:(int)((double)rand()/((double)RAND_MAX+1)*N)如果你不希望使用浮点,另一个方法是:rand()/(RAND_MAX/N+1)两种方法都需要知道RANDMAX,而且假设N要远远小于RANDMAX.
RANDMAX在ANSI里#dene在.
顺便提一下,RANDMAX是个常数,它告诉你C库函数rand()的固定范围.
你不可以设RANDMAX为其它的值,也没有办法要求rand()返回其它范围的值.
如果你用的随机数发生器返回的是0到1的浮点值,要取得范围在0到N1内的整数,只要将随机数乘以N就可以了.
参考资料:[K&R2,Sec.
7.
8.
7p.
168];[PCS,PCSSec.
11p.
172].
13.
15每次执行程序,rand()都返回相同顺序的数字.
你可以调用srand()来初始化模拟随机数发生器的种子,用的值可以是真正随机数或至少是个变量,例如当前时间.
这儿有个例子:第13章库函数76#include#includesrand((unsignedint)time((time_t*)NULL));不幸的是,这个代码并不完美——其中,time()返回的timet可能是浮点值,转换到无符号整数时有可能上溢,这造成不可移植.
参见问题:19.
36.
还要注意到,在一个程序执行中多次调用srand()并不见得有帮助;特别是不要为了试图取得"真随机数"而在每次调用rand()前都调用srand().
参考资料:[K&R2,Sec.
7.
8.
7p.
168];[ISO,Sec.
7.
10.
2.
2];[H&S,Sec.
17.
7p.
393].
13.
16我需要随机的真/假值,所以我用直接用rand()%2,可是我得到交替的0,1,0,1,0……这是个低劣的伪随机数生成器,在低位比特中不随机.
很不幸,某些系统就提供这样的伪随机数生成器.
尝试用高位比特;参见问题13.
14.
参考资料:[Knuth,Sec.
3.
2.
1.
1pp.
12-14].
13.
17怎样产生标准分布或高斯分布的随机数这里有一个由Marsaglia首创Knuth推荐的方法:#include#includedoublegaussrand(){staticdoubleV1,V2,S;staticintphase=0;doubleX;if(phase==0){do{doubleU1=(double)rand()/RAND_MAX;doubleU2=(double)rand()/RAND_MAX;V1=2*U1-1;V2=2*U2-1;S=V1*V1+V2*V2;}while(S>=1||S==0);X=V1*sqrt(-2*log(S)/S);}elseX=V2*sqrt(-2*log(S)/S);第13章库函数77phase=1-phase;returnX;}其它的方法参见本文的扩展版本,参见问题20.
36.
参考资料:[Knuth,Sec.
3.
4.
1p.
117];[Marsaglia&Bray];[Pressetal.
,Sec.
7.
2pp.
288-290].
13.
18我不断得到库函数未定义错误,但是我已经#inlude了所有用到的头文件了.
通常,头文件只包含外部说明.
某些情况下,特别是如果是非标准函数,当你连接程序时,需要指定正确的函数库以得到函数的定义.
#include头文件并不能给出定义.
参见问题10.
10,11.
29,13.
19,14.
3和19.
39.
13.
19虽然我在连接时明确地指定了正确的函数库,我还是得到库函数未定义错误.
许多连接器只对对象文件和函数库进行一次扫描,同时从函数库中提取适合当前未定义函数的模块.
所以函数库和对象文件(以及对象文件之间)的连接顺序很重要;通常,你希望最后搜索函数库.
例如,在Unix系统中,把-l参数放在命令行的后部.
参见问题13.
20.
13.
20连接器说end未定义代表什么意思这是个老Unix系统中的连接器所用的俏皮话.
当有其它符号未定义时,你才会得到end未定义的信息,解决了其它的问题,有关end的错误信息就会消失.
参见问题13.
18和13.
19.
13.
21我的编译器提示printf未定义!
这怎么可能据传闻,某些用于微软视窗系统的C编译器不支持printf().
你也许可以让这样的编译器认为你写的是"控制台程序",这样编译器会打开"控制台窗口"从而支持printf().
第13章库函数78第14章浮点运算14.
1一个oat变量赋值为3.
1时,为什么printf输出的值为3.
0999999大多数电脑都是用二进制来表示浮点和整数的.
在十进制里,0.
1是个简单、精确的小数,但是用二进制表示起来却是个循环小数0.
0001100110011.
.
.
.
所以3.
1在十进制内可以准确地表达,而在二进制下不能.
在对一些二进制中无法精确表示的小数进行赋值或读入再输出时,也就是从十进制转成二进制再转回十进制,你会观察到数值的不一致.
这是由于编译器二进制/十进制转换例程的精确度引起的,这些例程也用在printf中.
参见问题14.
6.
14.
2执行一些开方根运算,可是得到一些疯狂的数字.
确定你用了#include,以及正确说明了其它相关函数返回值为double.
另外一个需要注意的库函数是atof(),其原型说明在中.
参见问题14.
3.
参考资料:[CT&P,Sec.
4.
5pp.
65-6].
14.
3做一些简单的三角函数运算,也引用了#include,可是一直得到编译错误"undened:sin"(函数sin未定义).
确定你真的连接了数学函数库(mathlibrary).
例如,在Unix或Linux系统中,有一个存在了很久的bug,你需要把参数-lm加在编译或连接命令行的最后.
参见问题13.
18,13.
19和14.
2.
14.
4浮点计算程序表现奇怪,在不同的机器上给出不同的结果.
首先阅读问题14.
2.
如果问题并不是那么简单,那么回想一下,电脑一般都是用一种浮点的格式来近似的模拟实数的运算,注意是近似,不是完全.
下溢、误差的累积和其它非常规性是常遇到的麻烦.
不要假设浮点运算结果是精确的,特别是别假设两个浮点值可以进行等价比较.
也不要随意的引入"模糊因素";参见问题14.
5.
79第14章浮点运算80这并不是C特有的问题,其它电脑语言有一样的问题.
浮点的某些方面被通常定义为"中央处理器(CPU)是这样做的"(参见问题11.
34),否则在一个没有"正确"浮点模型的处理器上,编译器要被迫做代价非凡的仿真.
本文不打算列举在处理浮点运算上的潜在难点和合适的做法.
一本好的有关数字编程的书能涵盖基本的知识.
参见下面的参考资料.
参考资料:[K&P,Sec.
6pp.
115-8];[Knuth,Volume2chapter4];[Goldberg].
14.
5有什么好的方法来验对浮点数在"足够接近"情况下的等值浮点数的定义决定它的绝对精确度会随着其代表的值变化,所以比较两个浮点数的最好方法就要利用一个精确的阈值.
这个阈值和作比较的浮点数值大小有关.
不要用下面的代码:doublea,b;.
.
.
if(a==b)/*错!
*/要用类似下列的方法:#includeif(fabs(a-b).
而对于小的正整数指数,直接用乘法一般会更有效.
14.
8为什么我机器上的没有预定义常数MPI这个常数不包含在标准内,它应该是定义准确到机器精度的π值.
如果你需要用到π,你需要自己定义,或者用4*atan(1.
0)或acos(-1.
0)来计算出来.
参考资料:[PCS,Sec.
13p.
237].
第14章浮点运算8114.
9怎样测试IEEENaN以及其它特殊值许多实现高质量IEEE浮点的系统会提供简洁的工具去处理这些特殊值.
例如,在以非标准扩展功能,或可能以或提供预定义常数,及象isnan()这类的函数.
这些工具的标准化进程正在进行中.
一个粗陋但通常有效的测试NaN的方法:#defineisnan(x)((x)!
=(x))虽然一些不支持IEEE的编译器可能会把这个判断优化掉.
C99提供isnan(),fpclassify()及其它一些类别的例程.
必要时,还可以用sprintf()格式化需测试的值,在许多系统上,它会产生"NaN"或"Inf"的字符串.
你就可以比较了.
参见问题19.
38.
参考资料:[C9X,Sec.
7.
7.
3].
14.
10在C中如何很好的实现复数这其实非常直接,定义一个简单结构和相关的算术函数就可以了.
C99在标准中支持复数类别.
参见问题2.
8,14.
11.
参考资料:[C9X,Sec.
6.
1.
2.
5,Sec.
7.
8].
14.
11我要寻找一些实现以下功能的程序源代码:快速傅立叶变换(FFT)、矩阵算术(乘法、倒置等函数)、复数算术.
AjayShah整理了一个免费算术软件列表.
这个列表在互联网有广泛的归档.
其中一个URL是ftp://ftp.
math.
psu.
edu/pub/FAQ/numcomp-free-c.
参见问题18.
8,18.
10.
18.
16,18.
18.
14.
12TurboC的程序崩溃,显示错误为"oatingpointformatsnotlinked"(浮点格式未连接).
一些在小型机器上使用的编译器,包括TurboC(和Richie最初用在PDP-11上的编译器),编译时会忽略掉某些它认为不需要的浮点支持.
特别是用非浮点版的printf()和scanf()以节省一些空间,也就是忽略处理%e、%f和%g的编码.
然而,Borland用来确定程序是否使用了浮点运算的探测法并不充分,程序员有时必需调用一个空浮点库函数(例如sqrt(),或任何一个函数都可以)以强制装载浮点支持.
参见comp.
os.
msdos.
programmerFAQ以获取更多信息.
第14章浮点运算82第15章可变参数15.
1为什么调用printf()前,必须要用#include为了把printf()的正确原型说明引入作用域.
对于用可变参数的函数,编译器可能用不同的调用次序.
例如,如果可变参数的调用比固定参数的调用效率低.
所以在调用可变参数的函数前,它的原型说明必须在作用域内,编译器由此知道要用不定长调用机制.
在原型说明中用省略号来表示可变参数.
参考资料:[ISO,Sec.
6.
3.
2.
2,Sec.
7.
1.
7];[Rationale,Sec.
3.
3.
2.
2,Sec.
4.
1.
6];[H&S,Sec.
9.
2.
4pp.
268-9,Sec.
9.
6pp.
275-6].
15.
2为什么%f可以在printf()参数中,同时表示oat和double他们难道不是不同类型吗"参数默认晋级"规则适用于在可变参数中的可变动部分:char和shortint晋级到int,oat晋级到double.
同样的晋级也适用于在作用域中没有原型说明的函数调用,即所谓的"旧风格"函数调用,参见问题11.
4.
所以printf的%f格式总是得到double.
类似的,%c总是得到int,%hd也是.
参见问题12.
7,12.
13.
参考资料:[ISO,Sec.
6.
3.
2.
2];[H&S,Sec.
6.
3.
5p.
177,Sec.
9.
4pp.
272-3].
15.
3为什么当n为longint,printf("%d",n);编译时没有匹配警告我以为ANSI函数原型可以防止这样的类型不匹配.
当一个函数用可变参数时,它的原型说明没有也不能提供可变参数的数目和类型.
所以通常的参数匹配保护不适用于可变参数中的可变部分.
编译器不能执行内含的转换或警告不匹配问题1.
参见问题5.
2,11.
4,12.
7和15.
2.
15.
4怎样写一个有可变参数的函数用提供的辅助设施.
1译者注:现代的编译器(例如gcc),如果打开编译警告参数,编译器对标准中的可变参数函数(printf,scanf.
.
.
等)会进行匹配测试.
象问题中的源代码,用"gcc-Wall"进行编译,会给出这样的警告:"warning:intformat,longintarg(arg2)"83第15章可变参数84下面是一个把任意个字符串连接起来的函数,结果存在malloc的内存中:#include/*说明malloc,NULL,size_t*/#include/*说明va_相关类型和函数*/#include/*说明strcat等*/char*vstrcat(constchar*first,.
.
.
){size_tlen;char*retbuf;va_listargp;char*p;if(first==NULL)returnNULL;len=strlen(first);va_start(argp,first);while((p=va_arg(argp,char*))!
=NULL)len+=strlen(p);va_end(argp);retbuf=malloc(len+1);/*+1包含终止符\0*/if(retbuf==NULL)returnNULL;/*出错*/(void)strcpy(retbuf,first);va_start(argp,first);/*重新开始扫描*/while((p=va_arg(argp,char*))!
=NULL)(void)strcat(retbuf,p);va_end(argp);returnretbuf;}调用如下:char*str=vstrcat("Hello,","world!
",(char*)NULL);注意最后一个参数的类型重置;参见问题5.
2,15.
3.
注意调用者要释放返回的存储空间,那是用malloc分配的.
参考资料:[K&R2,Sec.
7.
3p.
155,Sec.
B7p.
254];[ISO,Sec.
7.
8];[Rationale,Sec.
4.
8];[H&S,Sec.
11.
4pp.
296-9];[CT&P,Sec.
A.
3pp.
139-141];[PCS,Sec.
11pp.
184-5,Sec.
13p.
242].
第15章可变参数8515.
5怎样写类似printf()的函数,再把参数转传给printf()去完成大部分工作用vprintf(),vfprintf()或vsprintf().
下面是一个error()函数,它列印一个出错信息,在信息前加入字符串"error:"和在信息后加入换行符:#include#includevoiderror(constchar*fmt,.
.
.
){va_listargp;fprintf(stderr,"error:");va_start(argp,fmt);vfprintf(stderr,fmt,argp);va_end(argp);fprintf(stderr,"\n");}参考资料:[K&R2,Sec.
8.
3p.
174,Sec.
B1.
2p.
245];[ISO,Secs.
7.
9.
6.
7,7.
9.
6.
8,7.
9.
6.
9];[H&S,Sec.
15.
12pp.
379-80];[PCS,Sec.
11pp.
186-7].
15.
6怎样写类似scanf()的函数,再把参数转传给scanf()去完成大部分工作C99支持vscanf(),vfscanf()和vsscanf(),C99以前的标准不支持.
参考资料:[C9X,Secs.
7.
3.
6.
12-14].
15.
7怎样知道实际上有多少个参数传入函数这一段信息不可移植.
一些旧系统提供一个非标准函数nargs().
然而它的可信度值得怀疑,因为它的典型返回值是参数的字节长度,而不是参数的个数.
结构、整数和浮点类型的值一般需要几个字节的长度.
任何接收可变参数的函数都应该可以从传入的参数本身来得到参数的数目.
类printf函数从格式字符串中的格式说明符来确定参数个数,就象%d这样的格式说明符.
所以如果格式字符串和参数数目不符时,此类函数会出错的很厉害.
还有一个常用的技巧,如果所有的参数是同一个类型,在参数列最后加一个标记值.
通常用0、-1或适当类型转换的空指针,参见问题5.
2和15.
4例子中exec1()和vstrcat()的用法.
最后,如果类型是可预见的,你可以加一个参数数目的参数.
当然调用者通常是很不喜欢这种做法的.
参考资料:[PCS,Sec.
11pp.
167-8].
第15章可变参数8615.
8为什么编译器不让我定义一个没有固定参数项的可变参数函数标准C要求用可变参数的函数至少有一个固定参数项,这样你才可以使用vastart().
所以编译器不会接受下面定义的函数:intf(.
.
.
){.
.
.
}参见问题15.
9.
参考资料:[ISO,Sec.
6.
5.
4,Sec.
6.
5.
4.
3,Sec.
7.
8.
1.
1];[H&S,Sec.
9.
2p.
263].
15.
9我有个接受oat的可变参函数,为什么vaarg(argp,oat)不工作"参数默认晋级"规则适用于在可变参数中的可变动部分:参数类型为oat的总是晋级(扩展)到double,char和shortint晋级到int.
所以vaarg(arpg,oat)是错误的用法.
应该总是用vaarg(arpg,double).
同理,要用vaarg(argp,int)来取得原来类型是char,short或int的参数.
基于相同理由,传给vastart()的最后一个"固定"参数项的类型不会被晋级.
参见问题11.
4和15.
2.
参考资料:[ISO,Sec.
6.
3.
2.
2];[Rationale,Sec.
4.
8.
1.
2];[H&S,Sec.
11.
4p.
297].
15.
10vaarg()不能得到类型为函数指针的参数.
宏vaarg()所用的类型重写不能很好地操作于象函数指针这类过度复杂的类型.
但是如果你用typedef定义一个函数指针类型,那就一切正常了.
参见问题1.
7.
参考资料:[ISO,Sec.
7.
8.
1.
2];[Rationale,Sec.
4.
8.
1.
2].
15.
11怎样实现一个可变参数函数,它把参数再传给另一个可变参数函数通常来说,你做不到.
理想情况下,你应该提供另一个版本的函数,这个函数接受valist指针类型的参数.
类似于vfprintf(),参见问题15.
5.
如果所有的参数必须完整的传给另一个函数,或者你不能重写另一个函数为一个接受valist指针类型参数的函数,这并没有一个可移植的解决方法.
也许可以通过求助于机器的汇编语言来实现.
参见问题15.
12.
第15章可变参数8715.
12怎样调用一个参数在执行是才建立的函数这没有一个保证工作或可移植的方法.
如果你好奇,可以问本文的编辑(SteveSummit),他有一些古怪的点子,也许你可以试试……也许你可以试着传一个无值型指针(void*)数组,而不是一个参数序列.
被调用函数遍历这个数组,就象main()遍历argv一样.
当然这一切都建立在你能控制所有的调用函数上.
参见问题19.
35.
第15章可变参数88第16章奇怪的问题16.
1遇到不可理解的不合理语法错误,似乎大段的程序没有编译.
检查是否有没有结束的注释,不匹配的#if/#ifdef/#ifndef/#else/#endif指令,又或者没有完成的引号.
记得还要检查头文件.
参见问题2.
14,10.
8和11.
28.
16.
2为什么过程调用不工作编译器似乎直接跳过去了.
代码是否看起来象这样:myprocedure;/*我的过程*/C只有函数,而函数调用总要用圆括号将参数括起来,即使是无参数的函数.
用下列代码:myprocedure();16.
3程序在执行用之前就崩溃了,用调试器单步跟进,在main()之前就死了.
也许你定义了一个或多个非常大的局部数组(超过上千字节).
许多系统只有固定大小的堆栈,即使那些自动动态堆栈分配的系统也会因为一次性要分配大段堆栈而失败.
一般对大规模数组,定义为静态的数组会更好.
如果由于递归的原因,每次都需要一组新的数组,可以用malloc()动态申请内存,参见问题1.
11.
参见问题11.
12,16.
4,16.
5和18.
4.
16.
4程序执行正确,但退出时崩溃在main()最后一个语句之后.
为什么会这样注意是否错误说明了main(),参见问题2.
14,10.
8,11.
12和11.
14.
是否把局部缓冲传给了setbuf()或setvbuf().
又或者问题出在注册于atexit()的清理函数.
参见问题7.
6和11.
17.
参考资料:[CT&P,Sec.
5.
3pp.
72-3].
89第16章奇怪的问题9016.
5程序在一台机器上执行完美,但在另一台上却得到怪异的结果.
更奇怪的是,增加或去除调试的打印语句,就改变了症状……许多地方有可能出错.
下面是一些通常的检查要点:未初始化的局部变量,参见问题7.
1.
整数上溢,特别是在一些16比特的机器上,一些中间计算结果可能上溢,象a*b/c,参见问题3.
11.
未定义的求值顺序,参见问题3.
1到3.
5.
忽略了外部函数的说明,特别是返回值不是int的函数,或是参数"缩小"或可变的函数.
参见问题1.
8,11.
4和15.
1.
复引用空指针,参见第5章.
malloc/free的不适当使用:假设malloc的内存都被清零、已释放的内存还可用、再次释放已释放内存、malloc的内部被破坏,参见问题7.
16和7.
17.
指针类常规问题,参见问题16.
7.
printf()格式与参数不符,特别是用%d输出longint,参见问题12.
7.
试图分配的内存大小超出一个unsignedint类型的范围,特别是在内存有限的机器上,参见问题7.
14和19.
27.
数组边界问题,特别是暂时的小缓冲,也许用于sprinf()来构造一个字符串,参见问题7.
1和12.
19.
错误的假设了typedef的映射类型,特别是sizet.
浮点问题,参见问题14.
1和14.
4.
任何你自己认为聪明的在特定机器上的机器代码生成小技巧.
正确使用函数原型说明能够捕捉到一些以上的问题.
lint会捕捉到更多.
参见问题16.
3,16.
4和18.
4.
16.
6为什么代码:char*p="hello,worl!
";p[0]='H';会崩溃字符串实字并不总是可以修改的,除非是用在字符数组的初试化.
试用:chara[]="hello,world!
";参见问题1.
13.
参考资料:[ISO,Sec.
6.
1.
4];[H&S,Sec.
2.
7.
4pp.
31-2].
第16章奇怪的问题9116.
7"Segmentationviolation","Buserror"和"Generalprotec-tionfault"意味着什么通常,这意味着你的程序试图访问不该访问的内存地址,一般是由于堆栈出错或是不正确的使用指针.
可能的原因有:局部数组溢出(用堆栈分配的自动变量);不小心,用了空指针(参见问题5.
2和5.
15)、未初始化指针、地址未对齐的指针或其它没有适当分配的指针(参见问题7.
1和7.
2);malloc内部被破坏(参见问题7.
16);函数调用参数不匹配,特别是如果用了指针,两个可能出错的函数是scanf()(参见问题12.
11)和fprintf()(确定他的第一个参数是FILE*).
参见问题16.
3和16.
4.
第16章奇怪的问题92第17章风格17.
1什么是C最好的代码布局风格K&R提供了最常被抄袭的实例,同时他并不要求大家沿用他的风格:大括号的位置并不重要,尽管人们对此有着执着的热情.
我们在几种流行的风格中选了一种.
选一个适合你的风格,然后坚持使用这一风格.
保持布局风格对自己,对邻近及通用源代码的一致比使之完美跟重要.
如果你的编码环境(本地习惯或公司政策)没有建议一个风格,你也不想发明自己的风格,可以沿用K&R中的风格.
各种缩进和大括号的放置之间的好与坏可以详尽而细致地探讨,但本文不再次重复了.
可以参见《印第安山风格指南》(IndianHillStyleGuide),问题17.
7.
"好风格"的品质并不简单,它包含的内容远远不止代码的布局细节.
不要把时间都花在格式上而忽略了更实质性的代码本身的质量.
参见问题10.
4.
参考资料:[K&R1,Sec.
1.
2p.
10];[K&R2,Sec.
1.
2p.
10].
17.
2用if(!
strcmp(s1,s2))比较两个字符串等值,是否是个好风格这并不是个很好的风格,虽然这是个流行的习惯用法.
如果两个字符串相等,这个测试返回为真,但!
("非")的使用,容易引起误会,以为测试不等值情况.
另一个选择是用一个宏:#defineStreq(s1,s2)(strcmp((s1),(s2))==0)参见问题17.
8.
17.
3为什么有的人用if(0==x)而不是if(x==0)这是用来防护一个通常错误的小技巧:if(x=0)如果你养成了把常量放在==前面的习惯,当你意外的把代码写成了:if(0=x)那编译器就会报怨.
明显的,一些人会觉得记住反换测试比记住输入双=号容易.
当然这个技巧只对和常量比较的情况有用.
参考资料:[H&S,Sec.
7.
6.
5pp.
209-10].
93第17章风格9417.
4原型说明externintfunc((int,int));中,那些多出来的括号和下划线代表了什么这是为了可以在使用ANSI以前的编译器时,关掉说明中的原型部分.
这是技巧的一部分.
在别的地方,宏被定义为类似下面的代码:#ifdef__STDC__#define__(proto)proto#else#define__(proto)()#endif原型说明中额外的括号是为了让原型列表被当作宏的单一参数.
17.
5为什么有些代码在每次调用printf()前,加了类型转换(void)printf()确实返回一个值,虽然极少数程序员去检验每次调用的返回值.
由于有些编译器和lint对于被丢弃的返回值会报警告,清楚的用(void)作类型转换相当于说:"我决定忽略这次调用的返回值,请继续对于其他忽略返回值的情况(也许是不应该的)提出警告.
"通常,无值类型转换也用于strcpy()和strcat()的调用,他们的返回值从不会令人惊讶.
参考资料:[K&R2,Sec.
A6.
7p.
199];[Rationale,Sec.
3.
3.
4];[H&S,Sec.
6.
2.
9p.
172,Sec.
7.
13pp.
229-30].
17.
6什么是"匈牙利标志法"(HungarianNotation)是否值得用匈牙利标志法是一种命名约定,由CharlesSimonyi发明.
他把变量的类型(或者它的预期使用)等信息编码在变量名中.
在某些圈子里,它被高度热爱,而在另一些地方,它被严厉地批评.
它的主要优势在于变量名就说明了它的类型或者使用.
它的主要缺点在于类型信息并不值得放在变量名中.
参考资料:[Simonyi&Heller].
17.
7哪里可以找到"印第安山风格指南"(IndianHillStyleGuide)及其它编码标准各种文档在匿名ftp都可以得到:地址文档及目录ftp.
cs.
washington.
edupub/cstyle.
tar.
Z(更新的印第安山风格指南,(IndianHillGuide))ftp.
cs.
toronto.
edudoc/programming(包括HenrySpencer的《C程序员的十诫》("10CommandmentsforCProgrammers"))ftp.
cs.
umd.
edupub/style-guide第17章风格95也许你会对这些书感兴趣:《TheElementsofProgrammingStyle》[K&P],《PlumHallProgrammingGuidelines》[Plum],《CStyle:StandardsandGuide-lines》[Straker].
参见文献[21].
参见问题18.
7.
17.
8有些人说goto是邪恶的,我应该永不用它.
那是否太极端了程序设计风格,就象写作风格一样,是某种程度的艺术,不可以被僵化的教条所束缚.
虽然风格的探讨经常都是围绕着这些条例.
对于goto语句,很早以前,就被注意到,随意的使用goto会很快的导致象面糊一样难以维护的代码.
然而,不经思考就简单的禁止goto的使用,并不能立即导至好程序.
一个无规划的程序员可以不用任何goto语句而构造出复杂难解的代码,也许使用奇怪的嵌套循环和布尔变量来取代goto.
通常,把这些程序设计风格的评论或者"条例"当作指导准则比当作条例要更好.
当程序员理解这些指导准则所要实现的目标,就会工作的更加之好.
盲目的回避某种构造或者死套条例而不融会贯通,最终还会导致这些条例试图避免的问题.
此外,许多程序设计风格的意见只是意见.
通常卷入"风格战争"是毫无意义的.
某些问题(象问题5.
3,5.
7,9.
2和10.
5),争辩的双方是不可能同意,认同对方的不同或者是停止争论的.
第17章风格96第18章工具和资源注意:本章中的信息比较旧,有些可能已经过时了,特别是各个公共包的URL.
18.
1常用工具列表.
工具程序名(参见问题18.
18)C交叉引用生成器cow,cxref,calls,cscope,xscope,ixfwC源代码美化器/美化打印cb,indent,GNUindent,vgrind版本控制和管理工具CVS,RCS,SCCSC源代码扰乱器(遮蔽器)obfus,shroud,opqcp"make"从属关系生成器makedepend,cc-M或cpp-M计算源代码度规工具ccount,Metre,lcount,csize;McCableandAssociates也有一个商业包出售C源代码行数计数器可以用UNIX的标准工具wc作个大概的计算,比用grep-c";"要好C说明帮助(cdecl)见comp.
sources.
unix第14卷(参见问题18.
18)和[K&R2]原型发生器参见问题11.
30malloc问题抓捕工具参见问题18.
2"选择性"的C预处理器参见问题10.
16语言翻译工具参见问题11.
30和20.
23C校对机(lint)参见问题18.
5C编译器参见问题18.
3这个工具列表并不完全,如果你知道有没列出的工具,欢迎联系本表的维护者.
其它工具列表和关于它们的讨论可以在Usenet的新闻组comp.
compilers和comp.
software-eng找到.
参见问题18.
3和18.
18.
97第18章工具和资源9818.
2怎样抓捕棘手的malloc问题有好几个调试工具包可以用来抓捕malloc问题.
其中一个流行的工具是ConorP.
Cahill的dbmalloc,公布在comp.
sources.
misc1992年第32卷.
还有leak公布在comp.
sources.
unix档案第27卷;"Snippets"合集中的JMalloc.
c,JMalloc.
h;MEMDEBUG(ftp://ftp.
crpht.
lu/pub/sources/memdebug);ElectricFence.
参见问题18.
18.
还有一些商业调试工具,对调试malloc等棘手问题相当有用:CodeCenter(Saber-C),出品CenterlineSoftware(http://www.
centerline.
com/)Insight(nowInsure),出品ParaSoftCorporation(http://www.
parasoft.
com/)Purify,出品RationalSoftware(http://www-306.
ibm.
com/software/rational/,原来是PureSoftware,现在是IBM的一部分)ZeroFault,出品TheZeroFaultGroup(http://www.
zerofault.
com/)18.
3有什么免费或便宜的编译器可以使用自由软件基金的GNUC(gcc,http://gcc.
gnu.
org/)是个流行而高质量的免费C编译器.
djgpp(http://www.
delorie.
com/djgpp/)是移植到MS-DOS的GCC版本.
据我所知,有移植到Macs和Windwos上的GCC版本.
1lcc是另外一个流行的编译器http://www.
cs.
virginia.
edu/lcc-win32/,http://www.
cs.
princeton.
edu/software/lcc/).
PowerC是MixSotfware公司提供的一个非常便宜的MS-DOS下的编译器.
公司地址:1132CommerceDrive,Richardson,TX75801,USA,214-783-6001.
ftp://ftp.
hitech.
com.
au/hitech/pacic是个MS-DOS下的试用C编译器.
非商业用途的不一定要注册.
新闻组comp.
compilers的档案中有许多有关各种语言的编译器、解释器、语法规则的信息.
新闻组在http://compilers.
iecc.
com/的档案包含了一个FAQ列表和免费编译器的目录.
参见问题18.
18.
18.
4刚刚输入完一个程序,但它表现的很奇怪.
你可以发现有什么错误的地方吗看看你是否能先用lint跑一遍(用-a,-c,-h,-p或别的参数).
许多C编译器1译者注:Windows下有两个移植版本cygwin(http://www.
cygwin.
com/)和MinGW(http://http://www.
mingw.
org/).
第18章工具和资源99实际上只是半个编译器,它们选择不去诊断许多源程序中不会妨碍代码生成的难点.
参见问题16.
5、16.
7和18.
5.
参考资料:[LINT].
18.
5哪里可以找到兼容ANSI的lintPC-Lint和FlexeLint是GimpelSoftware公司的产品(http://www.
gimpel.
com/).
UnixSystemV版本4的lint兼容ANSI.
可以从UNIXSupportLabs或SystemV的销售商单独得到(和其它C工具捆绑在一起).
另外一个兼容ANSI的lint是Splint(以前叫lclint,http://www.
splint.
org/).
它可以作一些高级别的正式检验.
如果没有lint,许多现代的编译器可以作出几乎和lint一样多的诊断.
许多网友推荐gcc-Wall-pedantic.
18.
6难道ANSI函数原型说明没有使lint过时吗实际上不是.
首先,原型说明只有在它们存在和正确的情况下才工作.
一个无心的错误原型说明比没有更糟.
其次,lint会检查多个源程序文档的一致性,以及数据和函数的说明.
最后,像lint这样独立的程序在加强兼容的、可移植的代码惯例上会比任何特定的、特殊实现的、充满特性和扩展功能的编译器更加谨慎.
如果你确实要用函数原型说明而不是lint来作多文件一致性检查,务必保证原型说明在头文件中的正确性.
参见问题1.
3和10.
4.
18.
7网上有哪些C的教程或其它资源有许多个:在http://cprog.
tomsweb.
net有个TomTorfs的不错的教程.
ChristopherSawtell写的《给C程序员的便筏》(NotesforCprogram-mers).
在下面的地址可以得到:ftp://svr-ftp.
eng.
cam.
ac.
uk/misc/sawtellC.
shar,ftp://garbo.
uwasa.
/pc/c-lang/c-lesson.
zip,http://www.
.
uib.
no/Fysisk/Teori/KURS/OTHER/newzealand.
html.
TimeLove的《程序员的C》(CforProgrammers).
http://www-h.
eng.
cam.
ac.
uk/help/tpl/languages/C/teachingC/TheCoronadoEnterprisesC教程在Simtel镜像点目录pub/msdos/c,或在http://www.
coronadoenterprises.
com/tutorials/c/index.
html.
SteveHolmes的在线教程http://www.
strath.
ac.
uk/IT/Docs/Ccourse/.
第18章工具和资源100MartinBrown的网页有一些C教程的资料http://www-isis.
ecs.
soton.
ac.
uk/computing/c/Welcome.
html.
在一些UNIX的机器上,在shell命令行,可以试试"learnc".
注意教程可能比较旧了.
最后,本FAQ的作者以前教授一些C的课程,这些笔记都放在了网上http://www.
eskimo.
com/scs/cclass/cclass.
html.
【不承诺申明:我没有检阅这些我收集的教程,它们可能含有错误.
除了那个有我名字的教程,我不能为它们提供保证.
而这些信息会很快的变得过时,也许,当你读到这想用上面的地址时,它们已经不能用了】这其中的几个教程,再加上许多其它C的信息,可以从http://www.
lysator.
liu.
se/c/index.
html得到.
VinitCarpenter维护着一个学习C和C++的资源列表,公布在新闻组comp.
lang.
c和comp.
lang.
c++,也归档在本FAQ所在(参见问题20.
36),或者http://www.
cyberdiem.
com/vin/learn.
html.
参见问题18.
8、18.
9和18.
16.
18.
8哪里可以找到好的源代码实例,以供研究和学习这里有几个连接可以参考:ftp://garbo.
uwasa.
/pc/c-lang/00index.
txt,http://www.
eskimo.
com/scs/src/.
小心,网上也有数之不尽的非常糟糕的代码.
不要从坏代码中"学习",这是每个人都可以做到的,你可以做的更好.
参见问题18.
7,18.
10,18.
16和18.
18.
18.
9有什么好的学习C的书有哪些高级的书和参考有无数有关C的书,我们无法一一列出,也无法评估所有的书.
许多人相信最好的书,也是第一本:由Kernighan和Richie编写的《TheCprogram-mingLanguage》("K&R",现在是第二版了[K&R2]).
对这本书是否适合初学者有不同的意见;我们当中许多人是从这本书学的C,而且学的不错;然而,有些人觉得这本书太客观了点,不大适合那些没有太多程序设计经验的人作为第一个教程.
网上有一些评注和勘误表,例如:http://www.
csd.
uwo.
ca/jamie/.
Refs/.
Footnotes/C-annotes.
html,http://www.
eskimo.
com/scs/cclass/cclass.
html,http://cm.
bell-labs.
com/cm/cs/cbook/2edis.
html.
许多活跃在新闻组comp.
lang.
c的人推荐K.
N.
King写的《C:AModernApproach》.
一本极好的参考书是由SamuelP.
Harbison和GuyL.
Steele和写的《C:AReferenceManual》[H&S].
虽然并不适合从头开始学C,本FAQ(英文版)已经出版了,见文献[CFAQ].
C和C++用户协会(AssociationofCandC++,ACCU)维护着一份很全面的有关C/C++的书目评论(http://www.
accu.
org/bookreviews/public/).
第18章工具和资源101参见问题18.
7.
18.
10哪里可以找到标准C函数库的源代码GNU工程有一个完全实现的C函数库(http://www.
gnu.
org/software/libc/).
另一个来源是由P.
J.
Plauger写的书《TheStandardCLibrary》[Plauger],然而它不是公共版权的.
参见问题18.
8,18.
16和18.
18.
18.
11是否有一个在线的C参考指南提供两个选择:http://www.
cs.
man.
ac.
uk/standardc/index.
html,http://www.
dinkumware.
com/htmcl/index.
html18.
12哪里可以得到ANSI/ISOC标准参见问题11.
2.
18.
13我需要分析和评估表达式的代码.
有两个软件包可用:"defunc",在1993年12月公布于新闻组comp.
sources.
misc(V41i32,33),1994年1月公布于新闻组alt.
sources.
可以在这个URL得到:ftp://sunsite.
unc.
edu/pub/packages/development/libraries/defunc-1.
3.
tar.
Z;"parse",可以从lamont.
ldgo.
columbia.
edu得到.
其它选择包括S-Lang注释器(http://www.
s-lang.
org/),共享软件Cmm("C减减"或"去掉困难部分的C").
参见问题18.
18和20.
4.
《SoftwareSolutionsinC》[Schumacher,ed.
]中也有一些分析和评估的代码(第12章,235到255页).
18.
14哪里可以找到C的BNF或YACC语法ANSI标准中的语法是最权威的.
由JimRoskind写的一个语法在ftp://ftp.
eskimo.
com/u/s/scs/roskindgrammar.
Z.
一个JeLee做的,新鲜出炉的ANSIC90语法工作实例可以在ftp://ftp.
uu.
net/usenet/net.
sources/ansi.
c.
grammar.
Z得到,还包含了一个相配的lexer.
FSF的GNUC编译器也含有一个语法,当然还有K&R的附录也有一个.
新闻组comp.
compilers的档案中含有更多的有关语法的信息,参见问题18.
3.
参考资料:[K&R1,Sec.
A18pp.
214-219];[K&R2,Sec.
A13pp.
234-239];[ISO,Sec.
B.
2];[H&S,pp.
423-435AppendixB].
第18章工具和资源10218.
15谁有C编译器的测试套件PlumHall(以前在Cardi,NJ,现在在Hawaii)有一个套件出售;RonaldGuilmette的RoadTestTM编译器测试套件(更多信息在ftp://netcom.
com/pub/rfg/roadtest/announce.
txt);Nullstone的自动编译器性能分析工具(http://www.
nullstone.
com).
FSF的GNUC(gcc)发布中含有一个许多编译器通常问题的C严酷测试.
Kahan的偏执狂的测试(ftp://netlib.
att.
com/netlib/paranoia),尽其所能的测试C实现的浮点能力.
18.
16哪里有一些有用的源代码片段和例子的收集BobStout的"SNIPPETS"是个很流行的收集(ftp://ftp.
brokersys.
com/pub/snippets或http://www.
brokersys.
com/snippets/).
LarsWirzenius的"publib"函数库(ftp://ftp.
funet.
/pub/languages/C/Publib/).
参考问题14.
11,18.
7,18.
8,18.
10和18.
18.
18.
17我需要执行多精度算术的代码.
一些流行的软件包是:"quad",函数在BSDUnix系统的libc中(ftp.
uu.
net,/systems/unix/bsd-sources/.
.
.
/src/lib/libc/quad/*);GNUMP函数库"libmp";MIRACL软件包(http://indigo.
ie/mscott/);DavidBell和LandonCurtNoll写的"'calc"程序;以及老Unix的libmp.
a.
参见问题14.
11和18.
18.
参考资料:[Schumacher,ed.
,Sec.
17pp.
343-454].
18.
18在哪里和怎样取得这些可自由发布的程序随着可利用程序数目,公共可访问的存档网站数目以及访问的人数的增加,这个问题回答起来变得即容易又困难.
有几个比较大的公共存档网站,例如:ftp.
uu.
net,archive.
umich.
edu,oak.
oakland.
edu,sumex-aim.
stanford.
edu和wuarchive.
wustl.
edu.
它们免费提供极多的软件和信息.
FSFGNU工程的中央发布地址是ftp.
gnu.
org.
这些知名的网站往往非常繁忙而难以访问,但也有不少"镜像"网站来分担负载.
在互联网上,传统取得档案文件的方法是通过匿名ftp.
对于不能使用ftp的人,有几个ftp-by-mail的服务器可供使用.
越来越多的,万维网(WWW)被使用在文件的宣告、索引和传输上.
也许还会有新的访问方法.
这些是问题中比较容易回答的部分.
困难的部分在于详情——本文不能追踪或列表所有的文档网站或各种访问的方法.
如果你已经可以访问互联网了,你可以取得比本文更加及时的活跃网站信息和访问方法.
问题的另一个即难也易的方面是找到哪个网站有你所要的.
在这方面有极多的研究,几乎每天都有可能有新的索引服务出台.
其中最早的服务之一第18章工具和资源103是"archie",当然还有许多高曝光的商业网络索引和搜索服务,例如AltaVista,Excite和Yahoo.
如果你可以访问Usenet,请查看定期发布在新闻组comp.
sources.
unix和comp.
sources.
misc的邮件,其中有说明新闻组归档的政策和怎样访问档案.
其中两个是:ftp://gatekeeper.
dec.
com/pub/usenet/comp.
sources.
unix/,ftp://ftp.
uu.
net/usenet/comp.
sources.
unix/.
新闻组comp.
archives包括了多数的有关各种东西的匿名ftp网站公告.
最后,通常新闻组comp.
sources.
wanted是个适合询问源代码的地方,不过在发贴前,请先查看它的FAQ"怎样查找资源(Howtondsources)".
参见问题14.
11,18.
8,18.
10和18.
16.
第18章工具和资源104第19章系统依赖19.
1怎样从键盘直接读入字符而不用等RETURN键怎样防止字符输入时的回显唉,在C里没有一个标准且可移植的方法.
在标准中跟本就没有提及屏幕和键盘的概念,只有基于字符"流"的简单输入输出.
在某个级别,与键盘的交互输入一般上都是由系统取得一行的输入才提供给需要的程序.
这给操作系统提供了一个加入行编辑的机会(退格、删除、消除等),使得系统地操作具一致性,而不用每一个程序自己建立.
当用户对输入满意,并键入RETURN(或等价的键)后,输入行才被提供给需要的程序.
即使程序中用了读入单个字符的函数(例如getchar()等),第一次调用就会等到完成了一整行的输入才会返回.
这时,可能有许多字符提供给了程序,以后的许多调用(象getchar()的函数)都会马上返回.
当程序想在一个字符输入时马上读入,所用的方式途径就采决于行处理在输入流中的位置,以及如何使之失效.
在一些系统下(例如MS-DOS,VMS的某些模态),程序可以使用一套不同或修改过的操作系统函数来扰过行输入模态.
在另外一些系统下(例如Unix,VMS的另一些模态),操作系统中负责串行输入的部分(通常称为"终端驱动")必须设置为行输入关闭的模态,这样,所有以后调用的常用输入函数(例如read(),getchar()等)就会立即返回输入的字符.
最后,少数的系统(特别是那些老旧的批处理大型主机)使用外围处理器进行输入,只有行处理模式.
因此,当你需要用到单字符输入时(关闭键盘回显也是类似的问题),你需要用一个针对所用系统的特定方法,假如系统提供的话.
新闻组comp.
lang.
c讨论的问题基本上都是C语言中有明确支持的,一般上你会从针对个别系统的新闻组以及相对应的常用问题集中得到更好的解答,例如comp.
unix.
questions或comp.
os.
msdos.
programmer.
另外要注意,有些解答即使是对相似系统的变种也不尽相同,例如Unix的不同变种.
同时也要记住,当回答一些针对特定系统的问题时,你的答案在你的系统上可以工作并不代表可以在所有人的系统上都工作.
星梦云怎么样?星梦云资质齐全,IDC/ISP均有,从星梦云这边租的服务器均可以备案,属于一手资源,高防机柜、大带宽、高防IP业务,一手整C IP段,四川电信,星梦云专注四川高防服务器,成都服务器,雅安服务器。星梦云目前夏日云服务器促销,四川100G高防4H4G10M月付仅60元;西南高防月付特价活动,续费同价,买到就是赚到!点击进入:星梦云官方网站地址1、成都电信年中活动机(成都电信优化线路,封锁...
hypervmart怎么样?hypervmart是一家国外主机商,成立于2011年,提供虚拟主机、VPS等,vps基于Hyper-V 2012 R2,宣称不超售,支持linux和windows,有荷兰和英国2个数据中心,特色是1Gbps带宽、不限流量。现在配置提高,价格不变,性价比提高了很多。(数据中心不太清楚,按以前的记录,应该是欧洲),支持Paypal付款。点击进入:hypervmart官方网...
rfchost怎么样?rfchost是一家开办了近六年的国人主机商,一般能挺过三年的国人商家,还是值得入手的,商家主要销售VPS,机房有美国洛杉矶/堪萨斯、中国香港,三年前本站分享过他家堪萨斯机房的套餐。目前rfchost商家的洛杉矶机房还是非常不错的,采用CN2优化线路,电信双程CN2 GIA,联通去程CN2 GIA,回程AS4837,移动走自己的直连线路,目前季付套餐还是比较划算的,有需要的可...