頭文件和函數聲明的另一個作用(轉)
阿新 • • 發佈:2017-10-24
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‘
|
這就是以錯誤的形式展現出來了,這就是函數聲明的作用。 它既告訴程序員如何調用一個函數,也讓編譯器檢查調用與函數原型是否一致。 有些人以為連接器會檢查參數匹配的問題,連接不出錯就萬事大吉了,這是不對的。你想,參數是以寄存器或壓棧的方式傳遞的。 編譯之後,參數類型和個數等信息都已丟失,連接器還能幫你查錯嗎? 它只是簡單地把名字相同的符號連接起來而已。
錯誤發現的越早越好
編程出現錯誤是不可避免的。錯誤發現的越早,修改的成本就越小。 因此原則是:盡量讓錯誤暴露出來(例如嚴格的編譯選項、測試),而不是掩蓋或忽視它。能在編譯時發現的錯誤,不要拖到運行時;能在編輯時發現的錯誤,不要拖到編譯時(許多編輯器的括號匹配、代碼補齊等功能就是為了減少這樣的錯誤)。
頭文件和函數聲明的另一個作用(轉)