c语言 隐式声明,关于C#:隐式函数声明和链接
最近,我了解了C語言中的隱式函數聲明。主要思想很明確,但在這種情況下,我對理解鏈接過程有些麻煩。
考慮以下代碼(文件a.c):
#include
int main() {
double someValue = f();
printf("%f
", someValue);
return 0;
}
如果我嘗試編譯它:
gcc -c a.c -std=c99
我看到有關函數f()的隱式聲明的警告。
如果我嘗試編譯和鏈接:
gcc a.c -std=c99
我有未定義的參考錯誤。 所以一切都很好。
然后添加另一個文件(文件b.c):
double f(double x) {
return x;
}
并調用下一個命令:
gcc a.c b.c -std=c99
令人驚訝的是,一切都成功地鏈接了。 當然,在./a.out調用之后,我會看到一個垃圾輸出。
因此,我的問題是:具有隱式聲明的函數的程序如何鏈接? 在我的示例中,在編譯器/鏈接器的作用下會發生什么?
我在SO上讀了許多這樣的話題,但仍然有問題。
首先,由于C99,從標準中刪除了函數的隱式聲明。編譯器可能支持此功能以編譯舊代碼,但這不是強制性的。引用標準序言,
remove implicit function declaration
也就是說,根據C11第6.5.2.2節
If the function is defined with a type that does not include a prototype, and the types of
the arguments after promotion are not compatible with those of the parameters after
promotion, the behavior is undefined.
所以,就您而言,
函數調用本身是隱式聲明(自C99以來已成為非標準聲明),
并且由于函數簽名的不匹配[假定函數的隱式聲明具有返回類型int],您的代碼將調用未定義的行為。
僅添加一點參考,如果您在調用后嘗試在同一編譯單元中定義函數,由于簽名不匹配,您將收到編譯錯誤。
但是,由于函數是在單獨的編譯單元中定義的(并且缺少原型聲明),因此編譯器無法檢查簽名。編譯之后,鏈接器將獲取目標文件,并且由于鏈接器中沒有任何類型檢查(并且目標文件中也沒有任何信息),因此可以愉快地鏈接它們。最后,它將最終成功完成與UB的編譯和鏈接。
感謝您的詳細回答。
這是正在發生的事情。
如果沒有f()的聲明,則編譯器會采用像int f(void)這樣的隱式聲明。然后愉快地編譯a.c。
編譯b.c時,編譯器沒有f()的任何先前聲明,因此可以從f()的定義中直觀地看出來。通常,您會在頭文件中放置f()的聲明,并將其同時包含在a.c和b.c中。因為兩個文件將看到相同的聲明,所以編譯器可以強制執行一致性。它將抱怨與聲明不匹配的實體。但是在這種情況下,沒有通用的原型可以參考。
在C中,編譯器不會在目標文件中存儲有關原型的任何信息,并且鏈接程序不會執行任何一致性檢查(不能)。它所看到的只是a.c中未解析的符號f和b.c中定義的符號f。它很高興地解析符號,并完成了鏈接。
但是,事情在運行時會失敗,因為編譯器會根據在此假設的原型在a.c中設置調用。哪個與b.c中的定義不匹配。 f()(來自b.c)將從堆棧中獲取一個垃圾參數,并將其返回為double,在a.c中返回時將被解釋為int。
我認為編譯器在處理隱式聲明時采用int f()而不是int f(void)。
病態檢查,但考慮到a.c中的調用沒有參數,因此假設int f(void)是有意義的,因為int f()表示any number of arguments。
隱式聲明在現代C語言中無效,并且是UB。不像int f(void);那樣提供隱式的。它僅提供類似int f();的聲明,編譯器在編譯b.c時不會" intuit",并且不需要事先定義原型。函數定義也提供其原型。如果以前提供過原型,則對其進行檢查。否則,不會。"通用原型"不是一回事。原型是否可用。與編譯器如何獲取該信息無關(通過頭文件或實際定義等)。
雖然(3)和(4)很好,但在標準中將其簡單定義為未定義的行為。
@ I3x所有這些都是問題的外圍部分,并且大多是在挑剔我表達某些觀點的方式。但是無所謂。
How are programmes with implicitly declared functions are linked? And what happens in my example under the hood of compiler/linker?
自C99以來,隱含的int規則已被C標準取締。因此,具有隱式函數聲明的程序是無效的。
自C99起無效。在此之前,如果沒有可見的原型,則編譯器會隱式聲明一個具有int返回類型的原型。
Surprisingly everything is linked successfully. Of course after
./a.out invocation I see a rubbish output.
由于您沒有原型,因此編譯器會為f()隱式聲明一個類型為int的原型。但是f()的實際定義返回double。兩種類型不兼容,這是未定義的行為。
即使在C89 / C90中,這也是未定義的,在C89 / C90中,隱式int規則有效,因為隱式原型與實際的f()返回類型不兼容。因此,此示例在所有C標準中均未定義(a.c和b.c)。
具有隱式函數聲明不再有用或無效。因此,有關編譯器/鏈接器處理方式的實際細節僅具有歷史意義。它可以追溯到K&R C的標準前時間,后者沒有函數原型,并且函數默認返回int。將功能原型以C89 / C90標準添加到C中。最重要的是,對于有效的C程序中的所有函數,必須具有原型(或在使用前定義函數)。
這還不完整。這個問題與隱式聲明無關,但是由于鏈接器對類型一無所知,他找到了一個名為f的函數的定義,該函數適用于鏈接。為了避免這種問題,某些語言使用名稱修飾將代碼類型編碼到函數名稱中。但是在C語言中不是這樣。擁有原型是不夠的,您需要一致的原型!
@ Jean-BaptisteYuns我已經解釋過,隱式原型的類型與實際類型不兼容,因此UB被調用。沒有"一致原型"之類的東西。原型是有關函數返回類型及其參數的完整信息。
@ i3x,但最大的問題(我認為)是鏈接為什么完成,以及解釋鏈接過程的詳細信息。
@ Jean-BaptisteYuns是的,主要的誤解是關于這種情況下的鏈接問題。因此,我閱讀了Sourav和l3x的答案。所以他們有點混在我的頭上。現在,Souravs的答案似乎更加完整。
@Ziffusion我是說它的UB,并且由于C.Btw的古老規則而被編譯和鏈接,我只是讀了您的答案,它并不完全準確。
@ i3x我想你錯過了重點。主要原因是:原型不一致(隱式聲明邊界效應)和無類型鏈接。兩者都與"古代C規則"無關。
編譯后,所有類型信息都將丟失(調試信息中可能除外,但鏈接程序不會對此進行注意)。唯一剩下的就是"在地址0xdeadbeef處有一個名為" f"的符號"。
標頭的目的是告訴C符號的類型,包括對于函數而言,它采用什么參數以及它返回什么。如果將實際值與聲明的值(顯式或隱式)不匹配,則會得到未定義的行為。
總結
以上是生活随笔為你收集整理的c语言 隐式声明,关于C#:隐式函数声明和链接的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 谷歌go语言课程讲解资源
- 下一篇: isvisible java_.NET(