C Puzzles详细分析

极客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   * =   & 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;

    
* =  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   * =  ( char   * ) & a;
if  ( * ==   0xff )
   printf(
" big endia " );
else
   printf(
" small endia " );

57、
int  main()
{
   
if  (printf( " hello world " <   0 )
   {}
}


你可能感兴趣的:(C Puzzles详细分析)