func(void):C 與 C++ 函式宣告的小差異

最近編譯遇到一個錯誤訊息 warning: function declaration isn’t a prototype,程式碼大概是像這樣:

hello.h

1 2 3 4
void foo() {     printf("Hello World!\n"); }
hello.c
1 2 3 4 5
int main(int argc, char**argv) {     foo();     return0; }
這樣的程式碼應該再單純不過了,怎麼會說我的函式宣告(Declaration)並不為一個原型(Prototype)呢? 深入了解 ANSI C 才知道原來這與語言的發展史有關。

函式的宣告(Declaration)、原型(Prototype)、與定義(Definition)

在公布解答前,先來解釋一下這三個名詞。

如果只是為了讓其他檔案知道有這個函式的存在,我們使用 Declaration 來代表告知的動作。雖然 C 語言並沒有支援 Function Overloading,我們不需要擔心可能會有同名的函式,但是卻有可能因為呼叫端傳入的參數不如預期,導致錯誤延遲到執行期間才被發現,會讓我們除錯的成本大幅度地增加。因此,為了在編譯階段就可以找出這些錯誤,ANSI C 導入了 Prototype 的觀念,除了與 Declaration 一樣具有告知的功能,還必須明確地表示出傳入的參數型態。
=> 也就是說 Prototype 就是 Declaration,但是 Declaration 未必是 Prototype

搞懂前兩個後,最後的 Definition 就簡單了,它就是函式的實作。舉例來說:

1 2 3 4 5 6
int foo();// Declaration int foo(int, int);// Prototype int foo(int a, int b)// Definition {     printf("a+b = %d\n", a+b); }
## Old-style Declaration

C89 出現之前(1983~),我們習慣稱我們所使用的 C 語言格式為 Pre-ANSI C (或稱 K&R C)。之所以特別強調是 old-style 的原因是在 ANSI C 的標準中規定 Declaration 也要包含 Prototype(C89 後,宣告與原型其實是同樣的東西,這也是容易讓我們年輕人搞混的原因XD),但為了相容老舊的程式碼,這種 old-style declaration 直到 C99 還是被允許的。

老舊的宣告格式所帶來的問題

在前面有稍微提到,之所以引入 Prototype 就是為了能在編譯階段提前找出錯誤。我們以下列程式碼範例來看看實際上可能會遇到的錯誤(範例碼參考自):

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
int imax(); int main(void) {     printf("%lu %lu %lu\n", sizeof(int), sizeof(double), sizeof(float));     printf("The maximum of %d and %d is %d.\n", 3, 5, imax(5));     printf("The maximum of %d and %d is %d.\n", 3, 5, imax(3.0, 5.0));     return0; } int imax(n, m) int n, m; {     int max;     if(n > m)         max = n;     else         max = m;     return max; }
程式執行結果如下:
4 8 4 The maximum of 3 and 5 is 1546027672. The maximum of 3 and 5 is 60391294.
看似再簡單不過的程式,但執行結果卻不是 3 也不是 5 ?!

以我執行環境來看,int/double/float 的大小分別為 4/8/4 bytes,因為 old-style 的宣告方式不會在編譯階段檢查傳入參數的正確性,因此在我們看來顯而易見的錯誤(程式行數少)就這樣被埋下了。

  • 第一個呼叫中,我們只傳入一個 4-byte int,函式執行時,卻去 stack 中 pop 兩個 4-byte int,而通常記憶體內容值要小於 5 的機率實在太小的,所以你會看到每次執行結果都不同,但是怎麼樣也不會是 5。
  • 第二個呼叫中,我們傳入了兩個浮點數,必須注意的是浮點數若沒有特別轉型,一律會被自動被視為 double 型態,也就是說我們傳入了兩個 8-byte double 而非兩個 4-byte float。

解決方法

經過以上的說明後,再回過頭看回到最先的問題 warning: function declaration isn’t a prototype 我想大家就會覺得這句很白話了,意思就是:「警告:函式宣告沒有明確寫出傳入的參數型態」。

常寫 C++ 的人可能會好奇問說:「我也沒加 void 也沒跳出警告啊?」。C++ 在這一部分的確沒有如 C 語言一般的包袱,也就是說,除非使用

extern "C" {}
明確告知編譯器這是 C 語言的程式片段,否則 C++ 是不允許 old-style declaration 的,

在 ANSI C 中,建議大家還是以 Prototype 來宣告函式,**如果真的沒有要傳入任何參數,也請加上 void **,這樣才可以讓編譯器在編譯階段自動檢查。

K&R, pages 72-73:

Furthermore, if a function declaration does not include arguments, as in
double atof(); that too is taken to mean that nothing is to be assumed about the arguments of atof; all parameter checking is turned off. This special meaning of the empty argument list is intended to permit older C programs to compile with new compilers. But it’s a bad idea to use it with new programs. If the function takes arguments, declare them; if it takes no arguments, use void.