在c89中,函式在呼叫前不一定非要宣告。如果沒有宣告,那麼編譯器會自動按照一種隱式宣告的規則,為呼叫函式的c**產生彙編**。下面是乙個例子:
int
main
(int argc,
char
** ar**)
單純的編譯上述源**,並沒有任何報錯,只是在鏈結階段因為找不到名為any_name_function的函式體而報錯。
[smstong@centos192 test]$ gcc -c main.c
[smstong@centos192 test]$ gcc main.o
main.o: in function
`main':
main.c:(.text+0x15): undefined reference to `
any_name_function'
collect2: ld 返回 1
之所以編譯不會報錯,是因為c89規定,對於沒有宣告的函式,自動使用隱式宣告。相當於變成了如下**:
int
any_name_function()
;int
main
(int argc,
char
** ar**)
引用自前面給出的例子,並不會造成太大影響,因為在鏈結階段很容易發現存在的問題。然而下面這個例子則會造成莫名的執行時錯誤。那麼函式宣告到底有宣告作用呢?
其實函式宣告的作用是讓編譯器幫你檢查函式呼叫時有沒有錯誤。比如引數的數量是否正確,如果呼叫函式時候少傳入乙個引數,並且沒有宣告該函式,編譯器無法知道你呼叫是否正確,只會提示乙個警告。很多人會忽略警告,導致最後程式執行時出現異常。
為什麼如果不宣告函式,編譯器發現不了錯誤?
編譯器在編譯過程中依次生成與原始檔對應的可重定位目標檔案(.o),每個原始檔中呼叫的函式在鏈結前都是以符號的形式體現在.o檔案中。在編譯過程中不會去檢查某個函式的形式,因為函式引數是通過暫存器和壓棧來處理的,直接把函式翻譯成符號,編譯器是不知道關於函式引數的資訊的,最後交給聯結器把符號翻譯成位址。所以鏈結的時候只要能找到對應得符號就不會報錯。
從現代c語言的角度來說,任何識別符號(除了goto的label以及main()的main)在使用之前都一定要宣告,函式也是如此。
理論上這是必須的,但某些編譯器為了遷就以前**的一些寫法放鬆了這個要求。所以還是應該寫,儘管看起來貌似不是必須的。
較新的c規範(c99、c11)是不允許不宣告直接用的。只是gcc預設還允許implicit function declaration功能。
#include
intmain
(int argc,
char
** ar**)
gcc編譯鏈結
[smstong@centos192 test]$ gcc -c main.c
main.c: 在函式『main』中:
main.c:6: 警告:隱式宣告與內建函式『sqrt』不相容
[smstong@centos192 test]$ gcc main.o
執行結果
1.000000
編譯時會給出警告,提示隱式宣告與內建函式』sqrt』不相容。gcc編譯器在編譯時能夠自動在常用庫標頭檔案(內建函式)中查詢與隱式宣告同名的函式,如果發現兩者並不相同,則會按照內建函式的宣告原型去生成呼叫**。這往往也是程式設計師預期的想法。
上面的例子中隱式宣告的函式原型為:
int
sqrt
(int
);
而對應的同名內建函式原型為:
double
sqrt
(double
);
最終編譯器按照內建函式原型進行了編譯,達到了預期效果。然而gcc編譯器的這種行為並不是c語言的規範,並不是所有的編譯器實現都有這樣的功能。同樣的原始碼在vc++2015下編譯執行的結果卻是:
vc++編譯
warning c4013: 「sqrt」未定義;假設外部返回 int
執行結果
2884223.000000
顯然,vc++編譯器沒有沒有所謂的「內建函式」,只是簡單的按照隱式宣告的原型,生成呼叫sqrt函式的**。由於返回型別和引數型別的不同,導致錯誤的函式呼叫方式,產生莫名奇妙的執行時錯誤。
對著這種情況,由於返回型別的不同,兩種編譯器都可以給出警告資訊,至少能引起程式設計師的注意。而下面這種情況,則更加隱蔽。
測試**如下:
#include
intmain
(int argc,
char
** ar**)
此時,由於隱式宣告的函式原型與gcc的內建函式原型完全相同,所以gcc不會給出任何警告,結果也是正確的。
而vc++則仍然會給出警告:warning c4013: 「abs」未定義;假設外部返回 int。
無論如何,隱式宣告的函式原型與庫函式完全相同,所以鏈結執行都是沒有問題的。
下面,稍微改動一下**:
#include
intmain
(int argc,
char
** ar**)
gcc編譯鏈結
[smstong@centos192 test]$ gcc -c main.c
[smstong@centos192 test]$ gcc main.o
沒有任何警告,可見,gcc的內建函式機制並不關心函式的引數,只是關心函式的返回值。
vc++編譯鏈結
warning c4013: 「abs」未定義;假設外部返回 int
雖然這個例子的執行結果都是正確的,但是這種正確是「碰巧」的,因為額外的函式引數並沒有影響到結果。這種偶然正確是程式中要避免的。
c語言的隱式函式宣告,給程式設計師帶來了各種困惑,給程式的穩定性帶來了非常壞的影響。不知道當初c語言設計者是如何考慮這個問題的?
對於gcc來說,前面給出的那個abs(-1,2,3,4)的特殊例子,編譯器根本不會產生任何警告,只能靠程式設計師熟悉自己呼叫的每乙個庫函式了。
為了避免這種問題,在c語言的c99版本中,無論如何都會給出警告。如gcc使用c99編譯上述**。
gcc -std=c99編譯
[smstong@centos192 test]$ gcc -c main.c -std=c99
main.c: 在函式『main』中:
main.c:5: 警告:隱式宣告函式『abs』
而c++則更嚴格,直接拋棄了隱式函式宣告,對於未宣告函式的呼叫,將直接無法通過編譯。
g++編譯
[smstong@centos192 test]$ g++ main.c
main.c: in function 『int main(int, char**)』:
main.c:5: 錯誤:『abs』在此作用域中尚未宣告
vc++編譯(作為c++)
error c3861: 「abs」: 找不到識別符號
在函式強型別這一點上,c++確實比c更嚴格,更嚴謹。 C語言中的隱式函式宣告
1 什麼是c語言的隱式函式宣告 在c語言中,函式在呼叫前不一定非要宣告。如果沒有宣告,那麼編譯器會自動按照一種隱式宣告的規則,為呼叫函式的c 產生彙編 下面是乙個例子 1 2 3 4 5 int main int argc,char argv 單純的編譯上述源 並沒有任何報錯,只是在鏈結階段因為找不...
C語言函式隱式宣告(1)
這段時間,在看中心後台服務軟體原始碼時發現,有很多自定義函式未經 宣告卻能在主程式中被呼叫,主程式中沒有包括上述函式的標頭檔案,我在各個目錄中也找不到上述函式的標頭檔案。這就奇怪了,連使用標準庫函式printf 都要包括標準輸入輸出標頭檔案,何況是自定義函式?這個問題困擾了我很久。前天問中創公司奚鍾...
C語言函式隱式宣告(2)
1 什麼是c語言的隱式函式宣告 在c語言中,函式在呼叫前不一定非要宣告。如果沒有宣告,那麼編譯器會自動按照一種隱式宣告的規則,為呼叫函式的c 產生彙編 下面是乙個例子 int main int argc,char argv 單純的編譯上述源 並沒有任何報錯,只是在鏈結階段因為找不到名為any nam...