首先,说这道题目“奇葩”并无任何不敬之意,相反它指出我知识的盲点,我是非常喜欢的。“奇葩”奇在你一般不该这么写代码。
今天参加一个知名外企的在线笔试,碰到一道另我小困惑了的C语言的题,题目如下:
#include <stdio.h> #include <stdlib.h> int i = 1; int main() { int static i; while(i<5) { printf("%d ",i); ++i; main(i); } return 0; }
请选择该程序的输出,选项如下:
A: 0 1 2 3 4 B: 1 2 3 4 5 C: runtime error D: 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(); //int f(int i);//会导致后面多处编译错误的声明方法; int main() { int static i; while(i<5) { foo(); foo(i); foo(i,i); printf("%d ",i); ++i; main(i); } return 0; } int foo(int i) { printf("%d ",i); }
输出为(经过freopen重定向至文件):
4227111 0 0 0 4227111 1 1 1 4227111 2 2 2 4227111 3 3 3 4227111 4 4 4
代码中需要注意的有两点:
1. foo函数的声明与定义不一致,而这并没有引起编译错误(想一想,这是否与我们固有的认知不符?);
2. 对于声明为参数列表为空的函数foo,无论使用多少参数它都可以工作;
依次分析这两点:
1. C语言要求函数的声明与定义一致,否则会有编译错误,但是存在例外:
C99标准关于函数声明一节(尾注2)中有如下一段话:
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.
意思是:
函数参数列表只会列出函数的参数。一个参数列表为空的函数声明,若它是函数定义体的一部分,说明这是一个没有参数的函数;
而若它不是函数定义体的一部分,则空的参数列表说明不提供任何函数参数数目与类型的信息。
对于上述代码,由于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 list specifies 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:敬请期待下集,函数调用过程中的参数压栈,究竟依据何处,函数声明、函数定义,还是函数调用?