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

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

  今天参加一个知名外企的在线笔试,碰到一道另我小困惑了的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:敬请期待下集,函数调用过程中的参数压栈,究竟依据何处,函数声明、函数定义,还是函数调用?

原文地址:https://www.cnblogs.com/ashowlym/p/3375294.html