C语言的谜题

本篇文章《C语言的谜题》展示了14个C语言的迷题以及答案,代码应该是足够清楚的,而且我也相信有相当的一些例子可能是我们日常工作可能会见得到的。通过这些迷题,希望你能更了解C语言。如果你不看答案,不知道是否有把握回答各个谜题?让我们来试试。

1、下面的程序并不见得会输出 hello-std-out,你知道为什么吗?

123456789101112#include <stdio.h>#include <unistd.h>int main(){    while(1)    {        fprintf(stdout,”hello-std-out”);        fprintf(stderr,”hello-std-err”);        sleep(1);    }    return 0;}

参考答案:stdout和stderr是不是同设备描述符。stdout是块设备,stderr则不是。对于块设备,只有当下面几种情况下才会被输入,1)遇到回车,2)缓冲区满,3)flush被调用。而stderr则不会。

2、下面的程序看起来是正常的,使用了一个逗号表达式来做初始化。可惜这段程序是有问题的。你知道为什么呢?

12345678#include <stdio.h>  int main(){    int a = 1,2;    printf(“a : %d
“,a);    return 0;}

参考答案:这个程序会得到编译出错(语法出错),逗号表达式是没错,可是在初始化和变量声明时,逗号并不是逗号表达式的意义。这点要区分,要修改上面这个程序,你需要加上括号: int a = (1,2);

3、下面的程序会有什么样的输出呢?

1234567#include <stdio.h>int main(){    int i=43;    printf(“%d
“,printf(“%d”,printf(“%d”,i)));    return 0;}

参考答案:程序会输出4321,你知道为什么吗?要知道为什么,你需要知道printf的返回值是什么。printf返回值是输出的字符个数。

4、下面的程序会输出什么?

123456789#include <stdio.h>int main(){    float a = 12.5;    printf(“%d
“, a);    printf(“%d
“, (int)a);    printf(“%d
“, *(int *)&a);    return 0;}

参考答案
该项程序输出如下所示,
0
12
1095237632
原因是:浮点数是4个字节,12.5f 转成二进制是:01000001010010000000000000000000,十六进制是:0×41480000,十进制是:1095237632。所以,第二和第三个输出相信大家也知道是为什么了。而对于第一个,为什么会输出0,我们需要了解一下float和double的内存布局,如下:

  • float: 1位符号位(s)、8位指数(e),23位尾数(m,共32位)
  • double: 1位符号位(s)、11位指数(e),52位尾数(m,共64位)

然后,我们还需要了解一下printf由于类型不匹配,所以,会把float直接转成double,注意,12.5的float和double的内存二进制完全不一样。别忘了在x86芯片下使用是的反字节序,高位字节和低位字位要反过来。所以:

  • float版:0×41480000 (在内存中是:00 00 48 41)
  • double版:0×4029000000000000 (在内存中是:00 00 00 00 00 00 29 40)

而我们的%d要求是一个4字节的int,对于double的内存布局,我们可以看到前四个字节是00,所以输出自然是0了。

这个示例向我们说明printf并不是类型安全的,这就是为什么C++要引如cout的原因了。

5、下面,我们再来看一个交叉编译的事情,下面的两个文件可以编译通过吗?如果可以通过,结果是什么?

file1.c

1int arr[80];

file2.c

1234567extern int *arr;int main(){    arr[1] = 100;    printf(“%d
“, arr[1]);    return 0;}

参考答案:该程序可以编译通过,但运行时会出错。为什么呢?原因是,在另一个文件中用 extern int *arr来外部声明一个数组并不能得到实际的期望值,因为他们的类型并不匹配。所以导致指针实际并没有指向那个数组。注意:一个指向数组的指针,并不等于一个数组。修改:extern int arr[]。(参考:ISO C语言 6.5.4.2 节)

6、请说出下面的程序输出是多少?并解释为什么?(注意,该程序并不会输出 “b is 20″)

12345678910111213141516#include <stdio.h>int main(){    int a=1;    switch(a)    {        int b=20;        case 1:            printf(“b is %d
“,b);            break;        default:            printf(“b is %d
“,b);            break;    }    return 0;}

参考答案:该程序在编译时,可能会出现一条warning: unreachable code at beginning of switch statement。我们以为进入switch后,变量b会被初始化,其实并不然,因为switch-case语句会把变量b的初始化直接就跳过了。所以,程序会输出一个随机的内存值。

7、请问下面的程序会有什么潜在的危险?

123456789#include <stdio.h>int main(){    char str[80];    printf(“Enter the string:”);    scanf(“%s”,str);    printf(“You entered:%s
“,str);    return 0;}

参考答案:本题很简单了。这个程序的潜在问题是,如果用户输入了超过80个长度的字符,那么就会有数组越界的问题了,你的程序很有可以及会crash了。

8、请问下面的程序输出什么?

12345678910#include <stdio.h>int main(){    int i;    i = 10;    printf(“i : %d
“,i);    printf(“sizeof(i++) is: %d
“,sizeof(i++));    printf(“i : %d
“,i);    return 0;}

参考答案:如果你觉得输出分别是,10,4,11,那么你就错了,错在了第三个,第一个是10没有什么问题,第二个是4,也没有什么问题,因为是32位机上一个int有4个字节。但是第三个为什么输出的不是11呢?居然还是10?原因是,sizeof不是一个函数,是一个操作符,其求i++的类型的size,这是一件可以在程序运行前(编译时)完全的事情,所以,sizeof(i++)直接就被4给取代了,在运行时也就不会有了i++这个表达式。

9、请问下面的程序的输出值是什么?

12345678910111213141516171819202122#include <stdio.h>#include <stdlib.h>  #define SIZEOF(arr) (sizeof(arr)/sizeof(arr[0]))#define PrintInt(expr) printf(“%s:%d
“,#expr,(expr))  int main(){    /* The powers of 10 */    int pot[] = {                    0001,                    0010,                    0100,                    1000                };      int i;    for(i=0;i<SIZEOF(pot);i++)        PrintInt(pot[i]);      return 0;}

参考答案:好吧,如果你对于PrintInt这个宏有问题的话,你可以去看一看《语言的歧义》中的第四个示例。不过,本例的问题不在这里,本例的输出会是:1,8,64,1000,其实很简单了,以C/C++中,以0开头的数字都是八进制的。

10、请问下面的程序输出是什么?(绝对不是10)

#include #define PrintInt(expr) printf(“%s : %dn”,#expr,(expr)) int main() { int y = 100; int *p; p = malloc(sizeof(int)); *p = 10; y = y/*p; /*dividing y by *p */; PrintInt(y); return 0; }

参考答案:本题输出的是100。为什么呢?问题就出在 y = y/*p;上了,我们本来想的是 y / (*p) ,然而,我们没有加入空格和括号,结果y/*p中的 /*被解释成了注释的开始。于是,这也是整个恶梦的开始。

11、下面的输出是什么?

12345678910#include <stdio.h>int main(){    int i = 6;    if( ((++i < 7) && ( i++/6)) || (++i <= 9))        ;      printf(“%d
“,i);    return 0;}

参考答案:本题并不简单的是考前缀++或反缀++,本题主要考的是&&和||的短路求值的问题。所为短路求值:对于(条件1 && 条件2),如果“条件1”是false,那“条件2”的表达式会被忽略了。对于(条件1 || 条件2),如果“条件1”为true,而“条件2”的表达式则被忽略了。所以,我相信你会知道本题的答案是什么了。

12、下面的C程序是合法的吗?如果是,那么输出是什么?

123456789101112#include <stdio.h>int main(){    int a=3, b = 5;      printf(&a["Ya!Hello! how is this? %s
"], &b["junk/super"]);      printf(&a["WHAT%c%c%c  %c%c  %c !
"], 1["this"],        2["beauty"],0["tool"],0["is"],3["sensitive"],4["CCCCCC"]);      return 0;}

参考答案
本例是合法的,输出如下:

Hello! how is this? super
That is C !

本例主要展示了一种另类的用法。下面的两种用法是相同的:

“hello”[2]
2["hello"]

如果你知道:a[i] 其实就是 *(a+i)也就是 *(i+a),所以如果写成 i[a] 应该也不难理解了。

13、请问下面的程序输出什么?(假设:输入 Hello, World)

123456789#include <stdio.h>int main(){    char dummy[80];    printf(“Enter a string:
“);    scanf(“%[^r]“,dummy);    printf(“%s
“,dummy);    return 0;}

参考答案:本例的输出是“Hello, Wo”,scanf中的”%[^r]“是从中作梗的东西。意思是遇到字符r就结束了。

14、下面的程序试图使用“位操作”来完成“乘5”的操作,不过这个程序中有个BUG,你知道是什么吗?

1234567891011121314151617#include <stdio.h>#define PrintInt(expr) printf(“%s : %d
“,#expr,(expr))int FiveTimes(int a){    int t;    t = a<<2 + a;    return t;}  int main(){    int a = 1, b = 2,c = 3;    PrintInt(FiveTimes(a));    PrintInt(FiveTimes(b));    PrintInt(FiveTimes(c));    return 0;}

参考答案:本题的问题在于函数FiveTimes中的表达式“t = a<<2 + a;”,对于a<<2这个位操作,优先级要比加法要低,所以这个表达式就成了“t = a << (2+a)”,于是我们就得不到我们想要的值。该程序修正如下:

123456int FiveTimes(int a){    int t;    t = (a<<2) + a;    return t;}

15. 此段程序的作者希望输出数组中的所有元素,但是他却没有得到他想要的结果,是什么让程序员和计算机产生歧义?

123456789101112#include <stdio.h>#define TOTAL_ELEMENTS (sizeof(array) / sizeof(array[0]))int array[] = {23,34,12,17,204,99,16};int main(){    int d;      for(d=-1;d <= (TOTAL_ELEMENTS-2);d++)        printf(“%d
“,array[d+1]);      return 0;}

解答:
运行上面的程序,结果是什么都没有输出,导致这个结果的原因是sizeof的返回值是一个unsinged int,为此在比较int d 和TOTAL_ELEMENTS两个值都被转换成了unsigned int来进行比较,这样就导致-1被转换成一个非常大的值,以至于for循环不满足条件。因此,如果程序员不能理解sizeof操作符返回的是一个unsigned int的话,就会产生类似如上的人机歧义。

16. 看上去非常完美的程序,是什么导致了编程程序不通过?

5678910111213141516171819202122232425262728293031323334353637383940#include <stdio.h>  void OS_Solaris_print(){    printf(“Solaris – Sun Microsystems
“);}  void OS_Windows_print(){    printf(“Windows – Microsoft
“);}  void OS_HP-UX_print(){    printf(“HP-UX – Hewlett Packard
“);}  int main(){    int num;    printf(“Enter the number (1-3):
“);    scanf(“%d”,&num);      switch(num)    {        case 1:            OS_Solaris_print();            break;        case 2:            OS_Windows_print();            break;        case 3:            OS_HP-UX_print();            break;        default:            printf(“Hmm! only 1-3 :-)
“);        break;    }    return 0;}

解答:
程序员要以计算机的语言进行思考,不上上面那段程序导致的结果不止是歧义这么简单,而直接的结果是,导致计算机”听不懂”你在说什么。导致计算机听不懂的原因是HP-UX中的’-’是减号?还是其他什么?

17. 下面这段程序会输出什么,为什么?

2345678910111213141516enum {false,true};  int main(){    int i=1;    do    {        printf(“%d
“,i);        i++;          if(i < 15)            continue;    }while(false);      return 0;}

解答:
1到14?不对,结果是1,因为continue的含义是不执行循环体之后语义,而直接到循环点。明显while(false)不属于循环体。导致这段程序的歧义就是:程序员没有完全理解计算机语言中continue的含义。

18. 下面这段程序的输出结果是:

1234567891011#include <stdio.h>#define f(a,b) a##b#define g(a)   #a#define h(a) g(a)  int main(){        printf(“%s
“, h(f(1,2)));        printf(“%s
“, g(f(1,2)));        return 0;}

当然,你首先要了解##和#的用法,如果不懂的话,本题你可以直接跳过。
解答:
看到这段程序你可能会认为,这两个printf输出的同一个结果,可是答案却非如此,本题的输出是12和f(1,2),为什么会这样呢?因为这是宏,宏的解开不象函数执行,由里带外。

19. 下面这段程序的输出是什么

#include <stdio.h>
int main()
{
int a=10;
switch(a)
{
case ’1′:
printf(“ONE
”);
break;
case ’2′:
printf(“TWO
”);
break;
defau1t:
printf(“NONE
”);

return 0;
}

解答:
这道题如果不仔细你可能永远都找不到答案,如果真到的到了那个时候,你是否会因为对default语义的怀疑,而不敢再使用default?本题的歧义点就是default,看好了是defau1t而不是default,不是关键字!为什么计算能”听懂”这样的defau1t,算然它听懂了,但它的理解却是标号”defau1t”

20. 下面这段程序的输出什么?

1234567891011121314151617#include <stdio.h>  int main(){    float f=0.0f;    int i;      for(i=0;i<10;i++)        f = f + 0.1f;      if(f == 1.0f)        printf(“f is 1.0
“);    else        printf(“f is NOT 1.0
“);      return 0;}

解答:
不要让两个浮点数相比较。所以本题的答案是”f is NOT 1.0″,如果你真想比较两个浮点数时,你应该按一定精度来比较,比如你一定要在本题中做比较那么你应该这么做if( (f � 1.0f)<0.1 )

21. 下面两个函数是否具有相同的原型?

12int foobar(void);int foobar();

下面这两段程序将会帮你找到答案
程序1

1234567891011121314151617181920#include <stdio.h>void foobar1(void){    printf(“In foobar1
“);}  void foobar2(){    printf(“In foobar2
“);}  int main(){    char ch = ’a';      foobar1();    foobar2(33, ch);       return 0;}

程序2

1234567891011121314151617181920#include “stdio.h”void foobar1(void){    printf(“In foobar1
“);}  void foobar2(){    printf(“In foobar2
“);}  int main(){    char ch = ’a';      foobar1(33,ch);    foobar2();      return 0;}

解答 程序片段一,没有问题,程序片段二编译报错,这两个程序告诉我们,foobar1(void)和foobar2()是有不同原型的的。我们可以在《ISO/IEC 9899》的C语言规范找到下面两段关于函数声明的描述

10.The special case of an unnamed parameter of type void as the only item in the list specifies that the function has no parameters

14.An identifier list declares only the identifiers of the parameters of the function. An empty list in a function declarator that is part of a definition of that function specifies that the function has no parameters. The empty list in a function declarator that is not part of a definition of that function specifies that no information about the number or types of the parameters is supplied.124)

上面两段话的意思就是:foobar1(void)是没有参数,而foobar1()等于forbar1(…)等于参数类型未知。

总结
看到这些C语言的题目,不禁让我想起了巴别塔,计算机语言作为如此严谨的语言都有可能带来如此多的歧义,更何况自然语言,更何况相互不通的自然语言。要杜绝歧义,我们就必须清晰的了解计算机语言每一个指令的语义。就如同人类,人类要和平就要相互了解各自的文化。愿世界上人们清晰了解别人的语言的语义,愿世界不再因为文化的不同而战争,原世界和平。

本文转载自http://coolshell.cn/articles/945.html          http://coolshell.cn/articles/830.html

发表评论

电子邮件地址不会被公开。 必填项已用 * 标注

您可以使用这些 HTML 标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>