极客C Puzzles。这里有常见的错误,也有易混淆的知识点,还有奇妙的算法,更有那些变态的用法。原题目都在这里:http://www.gowrikumar.com/c/,下面详细给出我的解答。
1.sizeof操作符返回值为size_t型,在windows/linux下为
typedef size_t unsigned int,而int和unsigned int进行比较时,编译器会自动把int转换为unsigned int,又因为int d = -1,所以转换之后溢出为正值4294967295,条件不成立。
2.这道题命很简单,仔细看一下就会发现
OS_HP-UX_print()中间那个字符“-”,变量和函数名只能由字母、数字还有下划线组成。
3.continue语句导致最近的循环语句的当前迭代结束,执行权被传递给条件计算部分。SO,只会打印一个1。
4.stdout换行缓冲,或者等缓冲区满了一并输出,而stderr无缓冲,即时输出,故一开始是看不到hello-out的。
5.这涉及到一个宏展开的问题。先来讲一下宏展开的步骤,以
#define MACRO_TEST(x) x为例,如果宏带有参数,例如x成为形参,调用的时候例如MACRO_TEST(z),那么z则成为实参:
第一步,实参替换形参,带入宏文本中; 第二步,如果实参也是宏,则继续展开该实参宏; 第三步,继续展开宏文本,如果文本中还有宏,继续展开,否则宏展开顺利结束。 上面第一步中,有一个特例,即字符串化预处理符#,如果遇到它,停止后面的宏展开,而是把这个参数当成字符串处理。SO,这一题就好解答了。第一个输出完全展开宏,为12,而第二个不完全展开,为f(1,2)。
6.
很简单,int和const char怎么比?//错误
正确的应该是default拼写错了。。。这个case语法上是对的,我理解成语义了,非常感谢网友们的指正。
7.
因为我的PC是32位的,所以无法测试,google一把,发现和CPU架构有关,IA-64是RISC,不能访问未对齐的地址。
8.和第13题一样,解法不同,结果相同。
9.浮点数不能直接比较。
10
.int a=1,2;这句被编译器认为是声明定义变量,而不是逗号表达式,2是常量,不能为变量名,非法。
11.printf返回值是实际输出值的个数,SO,值为4321。
12.这个看到register关键字,想来是底层应用,google之果然,duff's device,大大的有名。详细可以看
这个帖子的链接。
13.每次保留那些不是最后一位的1,循环几次,就是有几个1。这个函数针对unsigned int,其实对int也是适用的。
14、函数空参数列表在C语言中表示不定参数,仅有一个void表示没有参数,SO,program 1是编译不过的。
注意:C++中给前面两个函数传参数都是非法的。
15、这个值得好好研究,首先来看一下第一个输出汇编代码(MS V8生成)。
float a = 12.5;
00411576 fld dword ptr [__real@41480000 (4156C0h)] //将立即数放入浮点寄存器ST0
0041157C fstp dword ptr [a] //将ST0中的数据放入变量a中
printf("%d\n", a);
0041157F fld dword ptr [a] //再一次将变量a中的数据放入ST0
00411582 mov esi,esp //保存栈顶
00411584 sub esp,8 //调整栈顶指针ESP,注意这里调整的是8个字节,不是float的4个,说明内部按double转换了
00411587 fstp qword ptr [esp] //将ST0中的数据入栈
0041158A push offset string "%d\n" (415640h) //printf函数参数入栈
0041158F call dword ptr [__imp__printf (4182B8h)] //调用printf
00411595 add esp,0Ch //栈顶恢复
00411587 fstp qword ptr [esp]这句代码之后,我们来看看刚刚入栈的内容,这时候ESP=0012FE7C,查看内存,如下图
发现没有,头4个字节(红框部分),printf("%d")打印头四个字节,故而输出为0。
第二个输出就简单了,将变量a的数据强制为int输出。
16、类型T的指针和类型T的数组并非同种类型,程序试图写入0X00000004内存,崩溃。详细可以参考CCFAQ-0.9.4 ch6.1和ch6.2。
17、switch跳过第一个case或default之前的代码,输出结果不定。C++编译器(G++、VC)是编译不过的,提示a的初始化被case/default标签跳过了。
18、数组做参数传递时会退化成指针,函数void size(int arr[SIZE])等价于void size(int *arr),输出为4。
19、如果传给Error函数的参数字串包含格式化字符串怎么搞?例如,"test123...%d",那么输出可能为"test123...1245032",大大超出预期。
20、输入a,按回车,输出a,换行,换行,然后程序结束。这说明第二个scanf("%c", &c),这样不包含空格,直接读入了回车字符。当然,有空格是不读的。原因?C99 7.19.6.2第五条说的狠清楚:包含空白符的指令从输入中读取第一个非空白符字符,知道无字符可读。
21、数组溢出。
22、输出10,4,10,因为sizeof操作符实在编译时决定的,里面的i++不会起作用。
这里补充一下,sizeof('a')=?,C99中将'a'理解为整形字符常量,为int;C++中为字符字面量,为char。
23、指针类型不匹配。
24、不要看到逗号表达式就激动,等号优先级高于逗号,所以输出1,不是3。如果改成i=(1,2,3)则是3。
25、可能死循环、除数为0。
26、
不懂,以后有时间在研究
27、参考第5题。
28、算法原理,让两个int数字互掐,直到两人相等为止。如果是4个数求结果就是4强淘汰赛,分两队互掐,掐完的胜利者再互掐,呵呵。scanf返回的是输入的字符数目。
29、其实y=y/*p是注释语句。。。
30、同20题,scanf读取非‘-’字符。
31、scanf("%d\n", &n);
32、加号的优先级高于左移运算符。
33、三目运算符?:中不能使用return
34、i--一直小于20,循环到溢出为止。更改: n--。
35、ptr2是一个int型变量,不是指针。声明指针的最后方法是让*紧挨着放在变量名前面,例如:int *ptr1, *ptr2。
36、a没被初始化就运算也就算了,尽然还有除0这种严重错误。
37、条件表达式一旦一个不成立,立即转到下一个独立条件判断,汇编码可以很好的看出。打印8,不是9,也不是7。
38、指针a已经指导最后了,free崩溃。
39、这个题目貌似很变态
,别怕,还是拿出汇编来看问题。
printf(&a["ya!hello! how is this ? %s\n"], &b["junk/super"]);
00413734 mov eax,dword ptr [b]
00413737 add eax,offset string "junk/super" (415994h) //eax指向string "junk/super"的第5个字符
0041373C mov esi,esp
0041373E push eax
0041373F mov ecx,dword ptr [a]
00413742 add ecx,offset string "ya!hello! how is this ? %s\n" (415A80h) //ecx指向string 的第3个字符
00413748 push ecx
00413749 call dword ptr [__imp__printf (4182B8h)]
由汇编码来分析,其实相当于那一句C代码等价于printf("hello! how is this ? %s\n", "super")。
在来看,说明&b["junk/super"]相当于下面的这两行代码:
char arr[] = "junk/super";
const char *p = &arr[b];
即数组a[n]等价于n[a]。
第二行printf中的
1["this"]其实等价于"this"[1],即取出字符'h'。下面在来一次测试:
int a[] = {4, 5, 6, 7, 8};
int b = 3[a];
printf("%d\n", b);//输出7
原理参考C99 6.5.2.1第二条,E1[E2]的操作下表[]被解析为(*((E1)+(E2))),so,上面的代码就彻底清楚了。
40、读取字符知道遇到a为止,参见C99 7.19.6.2第12条。
41、a.c中的为声明,b.c中的为定义,main.c中的为外联声明,需要找定义,所以链接b.c中的变量定义,与a.c中的无关。
42、阅读了这题,才明白为什么C那么牛逼
。参考 ccfaq-0.9.4 第2.12问。offsetof(struct s, f)计算s结构中f域的偏移量。
43、SWAP(a++, b++)。
44、同第五题。
45、
int is_overflow(int *p, int a, int b)
{
long x, y, z;
x = (long)a;
y = (long)b;
*p = a + b;
z = (long)((unsigned long)x + y);
if ( (z^x) >= 0 || (z^b) >= 0 )
return 1;
else
return 0; //溢出
}
46、
看不懂
47、同43.
48、起码为VarArguments(const char *format, ...),可参考printf函数,不定参数不能只有这一个...。因为标准C要求用可变参数的函数至少有一个固定参数项,这样才可以使用va_start()。
补充:标准C++中可以这样定义,表示0个或多个参数,例如void fun(...);函数void fun(void)和函数void fun()意思完全一样,和C不同,见14题。49、
还真没想出来。。。
50、读取format字符串的长度存入变量中,例如printf("hello%n", &a),a的值为5。
51、
mov eax, [a]
add eax, [b]
52、%%
53、const char *p指向const char的非const指针,char* const p指向char的const指针。
54、memcopy可以重叠,而后者不可以。
55、%lf打印double,%f打印float
56、
short int a = 0x00ff;
char *p = (char *)&a;
if (*p == 0xff)
printf("big endia");
else
printf("small endia");
57、
int main()
{
if (printf("hello world") < 0)
{}
}