极客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生成)。
发现没有,头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、这个题目貌似很变态 ,别怕,还是拿出汇编来看问题。
在来看,说明&b["junk/super"]相当于下面的这两行代码:
第二行printf中的 1["this"]其实等价于"this"[1],即取出字符'h'。下面在来一次测试:
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、
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、
52、%%
53、const char *p指向const char的非const指针,char* const p指向char的const指针。
54、memcopy可以重叠,而后者不可以。
55、%lf打印double,%f打印float
56、
57、
这里有常见的错误,也有易混淆的知识点,还有奇妙的算法,更有那些变态的用法。
原题目都在这里: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,查看内存,如下图
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 //栈顶恢复
发现没有,头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")。
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)]
在来看,说明&b["junk/super"]相当于下面的这两行代码:
char
arr[]
=
"
junk/super
"
;
const char * p = & arr[b];
即数组a[n]等价于n[a]。
const char * p = & arr[b];
第二行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,上面的代码就彻底清楚了。
int b = 3 [a];
printf( " %d\n " , b); // 输出7
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; //溢出
}
{
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]
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 " );
char * p = ( char * ) & a;
if ( * p == 0xff )
printf( " big endia " );
else
printf( " small endia " );
57、
int
main()
{
if (printf( " hello world " ) < 0 )
{}
}
{
if (printf( " hello world " ) < 0 )
{}
}