2016-01-04 47 views
3

最近我已經瞭解到隱式函數聲明C。主要思想很明確,但在理解這種情況下的聯繫過程方面存在一些麻煩。隱函數聲明和聯動

考慮下面的代碼(文件變交流):

#include <stdio.h> 

int main() { 
    double someValue = f(); 
    printf("%f\n", 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的話題,如this,thisthis one,但仍然有問題。

回答

5

首先,自C99以來,函數的隱式聲明從標準中刪除。編譯器可能支持這種編譯遺留代碼,但它不是強制性的。引用標準的前言,

  • 刪除隱函數聲明

這就是說,按照C11,章§6.5.2。2

如果功能與不包括一個原型,和類型的 推廣後的參數是不與後 促進那些參數兼容的類型定義的,該行爲是未定義的。

所以,你的情況,

  • 函數調用本身是隱含的聲明(這成爲自C99非標),

  • 並且由於函數簽名不匹配[函數的隱式聲明被假定爲具有int返回類型],您的代碼調用undefined behavior

只需添加更多的參考,如果你嘗試後調用定義在相同編譯單元的功能,你會得到一個編譯錯誤,由於不匹配的簽名。但是,您的函數在單獨的編譯單元中定義(並且缺少原型聲明),編譯器無法檢查簽名。編譯之後,鏈接器接受目標文件,並且由於鏈接器中沒有任何類型檢查(並且目標文件中沒有任何信息),可以愉快地鏈接它們。最後,它將以成功彙編和鏈接 UB。

+1

感謝詳細的解答。 –

0

編譯後,所有類型的信息都會丟失(除非在調試信息中,但鏈接程序不會注意這一點)。唯一剩下的就是「地址0xdeadbeef處有一個名爲」f「的符號。

標題的要點是告訴C關於符號的類型,包括函數,它需要什麼參數以及它返回什麼。如果你與你聲明的那些(顯式或隱式)不匹配,你會得到未定義的行爲。

1

具有隱式聲明函數的程序是如何鏈接的?我的例子在編譯器/鏈接器的引擎下會發生什麼?

隱式int規則自C99以來已被C標準取締。所以它不是有效的有隱式函數聲明的程序。

從C99開始無效。在此之前,如果一個可見的原型不可用,那麼編譯器隱式聲明一個返回類型爲int

令人驚訝的是,一切都成功鏈接。當然,在調用 ./a.out之後,我看到垃圾輸出。

因爲你沒有原型,編譯器隱式聲明一個與int類型f()。但f()的實際定義返回double。這兩種類型是不兼容的,這是undefined behaviour

即使在隱式int規則有效的C89/C90中,這也是未定義的,因爲隱式原型與實際類型f()返回不兼容。所以這個例子是(在a.cb.c)是undefined在所有的C標準。

這不是有用或無效了有隱函數的聲明。所以編譯器/鏈接器如何處理的實際細節只是具有歷史意義。它回到K & R C的預標準時間,它沒有函數原型,函數默認返回int。 C89/C90標準中將函數原型添加到C中。底線,你必須必須具有有效的C程序中的所有功能原型(或使用前定義功能)。

+0

很好的解釋,謝謝。 –

+0

這不完整。這個問題與隱式聲明無關,但是由於鏈接器對類型一無所知,他發現了一個名爲'f'的函數的定義,並且適用於鏈接。爲了防止這樣的問題,一些語言使用名稱修改來將類型編碼到函數的名稱中;但是在C中並非如此。原型並不足夠,您需要一致的原型! –

+0

@ Jean-BaptisteYunès我已經解釋過隱式原型的類型和實際是不兼容的,UB是作爲結果調用的。沒有「一致原型」這樣的東西。原型是關於函數返回類型及其參數的完整信息。 –

2

這是發生了什麼事。

  1. 沒有爲f()聲明,編譯器假定像int f(void)一個隱含的聲明。然後愉快地編譯a.c
  2. 在編譯b.c時,編譯器沒有任何關於f()的預先聲明,所以它從f()的定義中直接得出結論。通常你會在頭文件中放一些f()的聲明,並將它包含在a.cb.c中。因爲這兩個文件都會看到相同的聲明,編譯器可以強制執行一致性。它會抱怨與聲明不符的實體。但在這種情況下,沒有共同的原型可以參考。
  3. C,編譯器不存儲有關原型在對象文件中的任何信息,並且鏈接器不執行一致性任何檢查(它不能)。所有它認爲是一個未解決的符號fa.c和符號fb.c定義。它高興地解決了符號,並完成了鏈接。
  4. 事情在運行時分解,但因爲編譯器建立在此基礎上假設有原型在a.c呼叫。這與b.c尋找的定義不符。 f()(從b.c)會得到一個垃圾論證出棧,並返回爲double,這將在a.c被解釋爲int上的回報。
+0

我認爲在處理隱式聲明時,編譯器假定'int f()'而不是'int f(void)'。 –

+0

我會檢查,但鑑於'a.c'中的調用沒有參數,假設'int f(void)'是有意義的,因爲'int f()'意味着任意數量的參數。 – Ziffusion

+1

隱式聲明在現代C中是無效的,並且是UB。隱式的不提供像'int f(void);'。它只提供像int f()這樣的聲明;編譯'b.c'時編譯器不會「intuit」,並且它不需要定義前面定義的原型。函數定義也提供了它的原型。如果之前提供了原型,則會對其進行檢查。否則,不。 「共同的原型」不是一回事。原型可用或不可用。編譯器如何獲取這些信息(通過頭文件或實際定義等)是無關緊要的。 –