2009/11/26:
2009/09/16:
2009/09/xx:
原题见
http://blog.chinaunix.net/u1/35320/showart_1773964.html1. sizeof的运算结果大多数情况下是无符号整形(具体与编译器有关),无符整形与有符号整型运算时,要将有符号整形提升为无符号数。所以-1会成为非常大的整数,因而使得for循环没有执行。为了避免这种问题,可以将TOTAL_ELEMENTS宏修改成如下:
#define TOTAL_ELEMENTS (int)(sizeof(array) / sizeof(array[0]))
或者使用更为彻底一些的办法:
#define sizeof (int)sizeof
2. 减号(-)不能作为函数名;
3. 不管continue是在while、for或do循环中,意思都是忽略剩余代码,重新进行循环条条判定(想一想,如果continue连循环条件判定都可以省略掉,那岂不成了无限循环)。所以程序的输出是:
1
4. 先是很多重复的hello-err,之后是重复出现的hello-out,然后按以上方式轮流重复出现。
原因:标准输出流stdout默认是行缓冲的,也就是说只有遇到换行符或缓冲区满才真正将数据输出;而标准输错流stderr默认是无缓冲的,一旦向流中输出马上显示。题目中给出的程序虽然向stdout输出,但由于没有换行符,缓冲区也没有满,所以反倒是输出到stderr的的内容先显示出来,一直到标准输出流缓冲区满之后,全部缓冲内容一次输出。
如果只是为了按代码次序在屏幕上输出内容,可以使用如下两种方法:
- 在向stdout输出前使用如下语句将将其设置为无缓冲
setvbuf(stdout, NULL, _IONBF, 0);
fprintf(stdout,"hello-out\n");
5. 宏展开时,如果遇到#或##,则对紧相连的的形参按实参字面值进行替换。是#时,作为字符串替换,是##时,作为记号替换。
展开
h(
f(
1,
2))时,由于h的定义体中没有#或##,所以对
f(
1,
2)进行展开,得到
g(
12),再展开得到"
12";
展开
g(
f(
1,
2))时,由于g的定义体中包含了#,所以只对形参按实参字面值进行替换,得到"
f(
1,
2)"。
6. 看清楚了,代码中的那个“defa1ut”有拼写错误(字母l和u位置应对调), 这样就相当于是定义了一个标号,而不是流程控制了。而且这种拼写错误极难发现,甚至在一些带语法高亮的编辑器那里,标号也是高亮显示的(如vim),真是防不胜防啊!除了更加仔细和重新设置编辑器的语法高亮外,其实还是有办法的。
- 编译时使用-Wall打开所有警告,会看到“标号xxx定义但未使用”的信息;
- 据说使用lint也可以,我安装了linux平台下的splint,但是即使用最严格的-strict参数,也未能检测出该问题,谁知道怎么回事?
7. 这个我不懂。
8. 函数的功能是计算输入参数的二进制模式中1的个数。算法的基本原理是:将所有二进制模式中的数字加起来就是要求的结果,只是算法使用了一种更高明更高效的方式(只用5次就可能计算完成),具体说来:
- 第一次,将二进制数中的相临两位上的数字相加,然后将相加结果(两位)再赋值到原来的位置。例如,拿最低两位是11的情况来说明,这两位上的数据相加结果为10(十进制的2,表示位模式中有2个1),再通过赋值,得到的结果最低两位是10。
- 第二次,将上面得到的结果继续处理,这次将相临4位数字按2个两位数相加(这个两位数就是上面分组计算得到的),4位的结果还是赋值到原来的位置;
- 按上述方法不计算剩余三次,最终得到结果。
9. 问题的根源在于,计算机中的数字都是用二进制表示的,而小数不能用二进制精确表示(十进制小数化为二进制的方法是乘2取整法,大家照此方法拿0.1试试,看能否精确表示),所以最终的结果也是不精确的。
至于我们经常讨论的浮点数比较问题,其根本原因也在于此。
10. 根据C语法
int a = 1,2;
是定义一个名为a的int变量,并初始化为1,定义名为2的int变量,没有初始值。数字2不能作为变量名,所以这是一个语法错误。
如果将代码改为
int a = (1,2);
则意思变为,定义一个名为a的int变量,并初始化为表达式(
1,
2)的结果,所以a的初始值为2。
11. 这绝对是一个有效的程序,运行结果为
4321
只要知道printf的返回值是输出字符个数就明白为什么了!
12.
Duff's device(达夫设备),通过减少比较次数和扩大循环体来优化循环的技巧。函数的功能是从from复制count个字节到to。使用她的理由就是:速度。
不过我认为这里面有一个bug,即当count为0时,还是会复制8个字节到to。
下面是我的修正代码:
void duff(register char *to, register char *from, register int count)
{
register int n=count/8;
switch(count%8){
case 0: while(--n >= 0){ *to++ = *from++;
case 7: *to++ = *from++;
case 6: *to++ = *from++;
case 5: *to++ = *from++;
case 4: *to++ = *from++;
case 3: *to++ = *from++;
case 2: *to++ = *from++;
case 1: *to++ = *from++;
}
}
}
13. 这是Brian Kernighan的方法(参考,另外,这里还有其他CountBits的实现方法)。
下面的语句每执行一次,x位模式中1的个数减1。当x等于0时,该语句执行的次数,即为位模式中1的个数。
x = x&(x-1);
14.
int foobar(
void)和
int foobar()当然不一样了,前者明确指出不带任何参数(如果带参数则出错),而后者则没有明确说明(编译时不会对参数进行检查)。
所以第2个程序会出现编译错误。
15. 首先说明,printf函数对后面参数的解释是按照格式串中的修饰符来进行的,也就是说,如果修饰符是"%d",那么不管后面对应的参数的类型是什么,它只取够整形需要的位数,并当作整形数处理。明白了这个道理,看一下两个printf语句传递了什么参数:
- 第一个printf使用的的参数是a,为float类型,根据类型提升的规则,要提升为double;
- 第二个printf使用的参数是表达式*(int *)&a,其计算结果为int型,是将float变量的前4字节作为int传入(在我的机器上sizeof(int)=4,sizeof(float)=4);
现在再来解释具体发生了什么:
- 第一个printf,将传入的double数据的前4字节当作int处理,由于a是一个正数而比较小,经提升后前面的位模式极可能为0,所以该句的输出为:0;
- 第二个printf,将传入的数据作为int输出,由于传入数据实际上是12.5(float)位模式的前4字节,现在当作int进行输出,结果是一个比较大的数字,在我的机器上这个结果是:1095237632。
16. 在我的机器上运行时,得到了一个段错误。
数组和指针是
不同的。数组名直接代表了数据存放的地址,而对于指针,其内容才代表数据的地址。访问过程也是不一样的。在以arr[i]的形式访问数据时,如果arr被声明为数组,其过程大致是:以arr以首地址偏移i个元素后,取得结果;当arr被声明为指针时,其过程为:取arr内容为首地址,在偏移i个元素的位置取得结果。
本题中将arr定义为数组,却但成指针使用,所以
arr[
1]先取数组内容为地址,再进行偏移访问,这样得到的地址极可能访问无效,因而发生了段错误。
17. switch-case匹配跳过了变量b的初始化,因而输出是一个随机数。
下面的代码也有这样的问题:
#include
int main()
{
goto print;
int b = 2;
print:
printf("b is %d\n", b);
return 0;
}
18. 结果是4。
数组名作为函数参数时,会被编译器转换(退化)为对应类型的指针。
请参考第205页的相关内容。
19. Error函数没有限制用户提供格式化串,这样会产生一个潜在在格式化串溢出bug。
请参考:
20. 通过运行程序,可以知道两个scanf的执行效果还是不一样的。个人认为格式化串中出现的连续空白字符(包括空格、制表符及换行符)将尽可能的与输入流中的连续空白字符匹配。所谓“尽可能”是指如果输入流中有空白字符,则匹配,如果没有,则忽略。
下面是实际的执行情况:
运行程序,执行第一个scanf,输入:
a回车 /*输入流中有两个字符,'a'和'\n'。 */
程序输出:
a /*scanf将'a'读到变量c中,所以输出a */
/*此时,输入流中还有一个字符'\n'。 */
/*执行第二个scanf,输入流中的'\n'与 */
/*格式串中的空格匹配,此时输入流为空,*/
/*导致%c没有输入匹配,所以等待用户输 */
/*入。 */
并且光标停留在下一行首,等待输入。再输入:
b回车 /*用户输入“b回车”后,输入流又有了两个*/
/*字符,'b'和'\n'。对剩下的%c进行匹,*/
/*将'b'读入到变量c中。 */
程序输出:
b /*输出变量c。输入流中剩余的内容被忽略。*/
同时程序结束。
上面第二个scanf格式串中的空格,匹配了前面输入中的回车。这让我想起上学时经常看到scanf后面跟着一个getchar函数,其目的也是为了吸收多余的回车。
如果将第二个scanf格式串中的空格去掉,重新运行程序,并且做同样的输入,那么第二个scanf会将回车读入到到变量c中,然后用
printf(
"%c\n",
c)输出,会得到两个换行。
21. scanf不会对接收缓冲区的长度进行检查,所以存在缓冲区溢出bug。建议使用fgets函数代替。
22. 表达式的值如果在编译时可以确定,则会在编译时计算。
sizeof(
i++)用于计算int变量占据的空间是编译时就知道的,因而程序时,该参数已经是常数4了。
23. 这个问题解释起来还是比较麻烦的,简单说来,是因为类型不相容。foo的参数要是指向只读字符指针的指针,而实参argv是指向可读写字符指针的指针。
详细的解释请参考《C专家编程》1.9节的内容。
24. 我能明确记得的运算符优先级之一就是逗点运算符,它的级别是最低的,所以赋值运算会先于它执行,运行结果为:
1
25.
双目运算符两边的操作数的计算顺序是标准没有定义的,是与具体实现相关的(具体请参考)。所以题目中的减法和除法运算有可能使用了错误的操作数。对于2-1的逆波兰运算,栈中的内容中应为21-(栈顶在末尾),如果先计算左侧的pop(),结果将是-1(1-2的结果)。除法与此类似。
26. 类似unix下的banner命令,将每个字符"放大"输出,但只支持英文字母(如果是小写则会转换为大写)。每个字母都是用6*6的字符矩阵显示,数组t就就是用于存储这26个大写字母的点阵。除最开始的6个0,其余整数每6个为一组表示一个字符的点阵,刚好是26组。每组中的6个整数代表从上到下的6个点阵行,每行只使用其低6位表示点阵行上的6个点。最开始的6个0是用于输出空白符的点阵(非字母字符都将用空白输出),只是实际上只使用了第一个整数(对于非字母字符,变量o的值总是0)。
外层的for循环用于输出所有字符的一行点阵,每输出完一行后换行。
内层的while循环每次取出字符串中的一个字符,将其视为字母转换为大写后,计算出对应的点阵行数字在数组中的下标。如果下标越界,表示本字符不是字母,点阵行的数字下标设定为0。
最内层的for循环总共循环7次,前6次用于输出点阵行中的点(1表示有点,用'#'输出,0表示无点,用' '输出),最后一次用于输出字符间的间隔符。
27. 注意:数组pot的前三个成员是8进制数,最后一个是10进制数。
28. 这个应该是欧几里德算法(即辗转相除法)的变形(详见
http://www.cnblogs.com/drizzlecrj/archive/2007/09/14/892340.html),把求余运算用减法运算代替。
scanf正常时返回实际处理的输入项个数,出错时返回EOF。
利用gcd函数实现的计算n个数最大公约数的函数:
int gcdn(int num[], int n)
{
ASSERT(n >= 2);
int i = 2;
int g = gcd(num[0], num[1]);
while (g != 1 && i g = gcd(g, num[i++]);
return g;
}
29.
y/*p中的/*会被作为注释处理,该行实际上是y=y;
这告诉我们,要避免在代码中使用嵌套注释,在用gcc编译时,加上-Wall参数,可对嵌套注释进行警告。
30. scanf函数的格式化串中,除空白符和修饰符外(关于空白符,请参考上面第20题),其他字符必须完全匹配才能得到正确的输入。所以那三个'-'也要输入,否则结果错误。
31. scanf接收数据的参数都必须是地址。scanf一行应改为:
scanf("%d\n",&n);
32. 加法的优先级高于移位运算。
33. 三元运算符要求操作数是有返回值的表达式,而不能是retrun语句。
34. 有以下三种方法
35.
ptr1是指向int的指针变量,
ptr2是int变量,两者类型不一样。所以两者之间的赋值以及把
ptr2当指针进行引用都是无法通过编译的。
36. 不会看到真正的结果,因为在输出结果之前程序程序因为除零,接收到SIGFPE信号,默认情况下进程收到此信号后会结束本进程。
37. 结果是:
8
第一个++i肯定是会执行的,但是由于逻辑操作符的短路处理,所以i++会被跳过,而最后的++i会被执行,这样++操作就执行了两次,结果为8。
38. 有两个问题:
39. C语言中计算数据元素地址的方法是首地址+偏移量,题目中的情况只是变成了偏移量+首地址,其实是一样的。
40. 输出结果为:
Life is be
"%[^a]"的意思是从输入流中提取字符串,只到遇到a停止(此时输入流中还剩下autiful等待提取)。
41. 一言已概之,与系统有关,大部分系统的行为与题目一致。详情请大家查阅第67页有关声明与定义的讨论。
42. 计算一个成员变量在结构体中的偏移量。
43. 有如下问题:
- a和b不能是同一个变量,即如果执行SWAP(a, a)那么不管原来a值是多少,执行后a值被置为0;
- a和b不能是浮点数,异或操作对浮点数没有意义;
- a和b不能是结体体等复合数据类型,原因同上;
- a或b不能是表达式;
44. #x 扩展后,使得x的实际内容成为字符串(如果x中含有\'"等字符,扩展时会自动在特殊字符前加\),所以,
DPRINTF(cnt);
宏展开后变为:,
printf("%s:%d\n","cnt",cnt);
45. 异号相同相加,必定不会溢出,两正数相加,有可能上溢,两负数相加,有可能下溢;
检测溢出的算法是:
两正数相加时,如果INT_MAX减去其中一个加数的差小于另一个加数,说明会上溢,否则不溢出;
两负数相加时,如果INT_MIN减去其中一个加数的差大于另一个加数,说明会下溢,否则不溢出;
编写的代码如下:
int IAddOverFlow(int* result,int a,int b) { *result = a + b;
if ((a>=0)!= (b>=0)) return 0; else if (a>=0) return (INT_MAX - a < b) ? 1 : 0; else return (INT_MIN - a > b) ? 1 : 0; }
|
有关溢出的其他检测方法,请参考:
46. 还没看出来,谁会?请指点!