从一道比较奇葩的笔试题说起

  首先,说这道题目“奇葩”并无任何不敬之意,相反它指出我知识的盲点,我是非常喜欢的。“奇葩”奇在你一般不该这么写代码。

  今天参加一个知名外企的在线笔试,碰到一道另我小困惑了的C语言的题,题目如下:

#include <stdio.h>#include <stdlib.h>int i = 1;int main(){int static i;while(i<5){printf(,i);++i;main(i);}return 0;}

  请选择该程序的输出,选项如下:

A: B: C: runtime errorD: compile error

  分析:

  题目的主要考点是:

  1.能否认识到,main函数其实也是普通的函数,进行递归调用没有问题。

  2.变量作用域的问题,认识到这一点容易知道答案应该选择A。(每次对i的引用都是对局部静态变量的引用)

  然而,令博主颇有些困惑的地方是: 对于函数定义参数列表为空的main函数,调用时却对其赋了值,这是否会引起编译时错误呢。平常的经验告诉我,调用声明参数列表为空的函数,向其传入参数没有问题,程序可以正常运行。往深问一层,这是为什么呢?(尾注1)

  我进行了如下的测试(编译环境gcc4.4.1, 编译选项mingw32-gcc.exe -Wall -ansi -g -std=c99):

#include <stdio.h>#include <stdlib.h>int i = 1;int foo(); main(){int static i;while(i<5){foo();foo(i);foo(i,i);printf(,i);++i;main(i);}return 0;}int foo(int i){printf(,i);}

  输出为(经过freopen重定向至文件):

  代码中需要注意的有两点:

1.foo函数的声明与定义不一致,而这并没有引起编译错误(想一想,这是否与我们固有的认知不符?);

2.对于声明为参数列表为空的函数foo,无论使用多少参数它都可以工作;

  依次分析这两点:

1. C语言要求函数的声明与定义一致,否则会有编译错误,但是存在例外:

  C99标准关于函数声明一节(尾注2)中有如下一段话:

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

  意思是:

函数参数列表只会列出函数的参数。一个参数列表为空的函数声明,若它是函数定义体的一部分,说明这是一个没有参数的函数;而若它不是函数定义体的一部分,则空的参数列表说明不提供任何函数参数数目与类型的信息。

  对于上述代码,由于foo函数的第一个声明中参数列表为空,而此声明不是函数定义的一部分,则说明此时没有任何函数参数的信息,那么编译器也就不会对之后的三次函数调用(函数符号的引用)进行参数检查。于是,一段不符合常理的代码编译通过了。

2. 如前所述,此时编译器不会对foo函数的调用进行参数检查,而我们的执行结果也容易解释了。函数foo被调用之前,向其传递的参数会被压入栈中,foo函数执行时从栈指针固定位置取值作为i进行打印,也就出现了4227111(这是个动态的值)的情况。

  由此可见,提供一个参数不详的函数声明,有导致危险行为的潜力(比如foo函数中有对字符串形参的打印)。虽然这某种程度会增加编程的灵活性,但是其危险性更大,,可能这也能算是C语言一个缺陷级的设计。

  然而,这里似乎出现了与我们看到现象不一致的问题。第一份代码中的main函数的调用,main函数的唯一一个声明正是它定义的一部分,根据C99标准,它是定义的一部分,它的参数列表为空,所以它指明函数是没有参数的。然而,我们向main函数传入任意个参数的时候,却并未引起编译错误。

  再翻看标准,看到一个需要注意的地方:

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

  即:参数列表中只有一个没有名字的void型的参数的这种特殊情况,指定函数是没有参数的。

  所以,只能暂下结论,在gcc 4.4.1的环境里:

1.对于参数列表为空的函数声明,即使它是函数定义的一部分,对他进行调用时传入参数是不会导致编译时错误的,虽然这与标准并不一致(尾注3);

2.看到参数列表为空的函数声明时,若它不是函数定义的一部分,请敏感起来;

3.培养起良好的习惯,把空的参数列表写成void吧。

====================================尾注==========================================

尾注1:最近参加不少外企的笔试,碰到一些综合性的C语言的题目,感慨毕竟是有底蕴的大公司啊,各种在C上血虐我。(比如:oracle)。

尾注2:6.7.5.3 Function declarators(including prototypes) 原谅博主的懒惰,忙于找工作,暂时没时间看C11了。

尾注3:敬请期待下集,函数调用过程中的参数压栈,究竟依据何处,函数声明、函数定义,还是函数调用?

既有美妙的风景,也会有称不上景、只有风的地方。

从一道比较奇葩的笔试题说起

相关文章:

你感兴趣的文章:

标签云: