從一道比較奇葩的筆試題說起


  首先,說這道題目“奇葩”並無任何不敬之意,相反它指出我知識的盲點,我是非常喜歡的。“奇葩”奇在你一般不該這么寫代碼。

  今天參加一個知名外企的在線筆試,碰到一道另我小困惑了的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\n",i);
        ++i;
        main(i);
    }

    return 0;
}

int foo(int i)
{
    printf("%d\t",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:敬請期待下集,函數調用過程中的參數壓棧,究竟依據何處,函數聲明、函數定義,還是函數調用?

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM