1. 程式人生 > >頭文件和函數聲明的另一個作用(轉)

頭文件和函數聲明的另一個作用(轉)

alt foo log 繼續 開始 logs man 過去 proc

頭文件的另一個作用,定義函數接口,作用似乎沒那麽大,因為編譯、連接都通過了,程序也能運行了,這不就行了嗎。下面我們用 一個例子說明這個問題。

假設我們寫了一個很簡單的程序: main調用了一個函數foo:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <stdlib.h> #include <stdio.h> int main(void) { int i; i = foo(2, 3); printf("foo returns %d\n", i); exit
(0); } int foo(int a) { return a + a; }

此程序有嚴重的錯誤,但是如果我們用命令

1 $ gcc -c main.c

編譯的時候,沒有任何警告或出錯信息。好,我們加上-Wall選項:

1 2 3 $ gcc -c -Wall main.c main.c: In function `main‘: main.c:8: warning: implicit declaration of function `foo‘

這句implicit declaration of function

可能是被程序員忽視最多的警告了。 好,我們繼續忽視它,接下來連接也能通過:

1 $ gcc -o ex1 main.o

運行也沒有問題。 但你不覺得毛骨悚然嗎? 一個嚴重的錯誤就這樣從你眼皮底下過去了。你的程序越來越復雜,這個警告混在一大堆編譯信息裏,根本就註意不到了。 直到某一天一些奇怪的問題出現了,你開始調用各種土槍洋炮來調試程序…

其實,如果我們稍微尊重些編譯器,把函數的聲明加在main的前面,問題錯誤馬上顯現:

1 2 int foo (int a); int main(void)

重新編譯

1 2 3 $ gcc -c -Wall main.c main.c: In function `main‘: main.c:9: error: too many arguments to function `foo‘

這就是以錯誤的形式展現出來了,這就是函數聲明的作用。 它既告訴程序員如何調用一個函數,也讓編譯器檢查調用與函數原型是否一致。 有些人以為連接器會檢查參數匹配的問題,連接不出錯就萬事大吉了,這是不對的。你想,參數是以寄存器或壓棧的方式傳遞的。 編譯之後,參數類型和個數等信息都已丟失,連接器還能幫你查錯嗎? 它只是簡單地把名字相同的符號連接起來而已。

錯誤發現的越早越好

編程出現錯誤是不可避免的。錯誤發現的越早,修改的成本就越小。 因此原則是:盡量讓錯誤暴露出來(例如嚴格的編譯選項、測試),而不是掩蓋或忽視它。能在編譯時發現的錯誤,不要拖到運行時;能在編輯時發現的錯誤,不要拖到編譯時(許多編輯器的括號匹配、代碼補齊等功能就是為了減少這樣的錯誤)。

頭文件和函數聲明的另一個作用(轉)