2010-01-08 171 views
10

我已閱讀關於SO上的外部/內部鏈接的現有問題。我的問題不同 - 如果我在CC++的不同翻譯單元中使用外部鏈接對同一變量進行多重定義,會發生什麼情況?C和C++之間的鏈接差異?

例如:

/*file1.c*/ 

typedef struct foo { 
    int a; 
    int b; 
    int c; 
} foo; 

foo xyz; 


/*file2.c*/ 

typedef struct abc { 
    double x; 
} foo; 

foo xyz; 

使用開發 - C++和作爲一個C程序,上述程序編譯和鏈接完美;而如果將其編譯爲C++程序,則會出現多重定義錯誤。爲什麼它應該在C下工作,C++有什麼區別?這種行爲是未定義的還是編譯器依賴的?這段代碼有多「壞」,如果我想重構它,我該怎麼做(我遇到過很多這樣寫的舊代碼)?

回答

4

C和C++都有一個「一個定義規則」,即每個對象只能在任何程序中定義一次。這條規則的違反導致未定義行爲這意味着你可能會或可能不會進行編譯時看到診斷信息。

有文件範圍內下面的聲明之間的語言差異,但它並不直接與你的榜樣關心的問題。

int a; 

在C這是一個試探性的定義。它可能與同一翻譯單元中的其他暫定義相結合以形成單一定義。在C++中它始終是一個定義(你必須使用extern來聲明對象而不定義它),並在相同的翻譯單元相同的對象的任何後續定義一個錯誤。

在您的例子都翻譯單元有一個(有衝突)的xyz從他們的初步定義的定義。

0

C程序允許這樣做,並將內存視爲一個聯合。它會運行,但可能不會給你你所期望的。

C++程序(這是更強類型)正確地檢測到問題並要求您修復它。如果你真的想要一個聯盟,就把它聲明爲一個。如果你想要兩個不同的對象,請限制它們的範圍。

+1

的C行爲可能是您實現真正的,但它不是由語言保證。 – 2010-01-08 08:34:08

+0

變量名稱只是內存地址的標籤。如果您提供了兩種關於如何解釋該標籤的定義,那麼這並不奇怪地使標籤引用兩個不同的對象。 你有沒有看過一個鏈接器會有不同的表現? – 2010-01-08 08:50:41

+0

我不否認這是通常的鏈接器行爲,這種行爲被其他語言和許多C實現使用。然而,你的回答意味着它是一個明確定義的行爲。根據C標準附錄J,允許在程序中使用多個外部定義是一種常見的擴展,但即使使用此擴展,如果定義不一致,則會導致未定義的行爲。 – 2010-01-08 10:26:54

1

C++不允許多次定義符號。不確定C鏈接器在做什麼,一個好的猜測可能是它只是將兩個定義映射到同一個符號上,這當然會導致嚴重的錯誤。

對於移植,我會嘗試將單個C文件的內容放入匿名命名空間,這實際上使符號不同,並且是文件本地,因此它們不會與別處的同名衝突。

+0

確定它可以被定義不止一次。但是定義必須相同。 – Potatoswatter 2010-01-08 08:22:45

+1

@Patatoswatter:對象只能_defined_一次,它們可能被多次聲明。 「內聯」函數的特殊之處在於它們可以在每個翻譯單元中定義一次,但其他函數只能在每個程序中定義一次。 – 2010-01-08 08:28:07

+0

對不起,是我不好:P – Potatoswatter 2010-01-08 08:31:57

2

這是由C++的名稱造成的。從Wikipedia

第一C++編譯器被 實現爲翻譯到C源代碼 ,這將隨後通過 C編譯器的對象代碼被編譯;因爲 這個,符號名稱必須符合 C標識符規則。即使後來, 與 直接生成機器碼或程序集 的編譯器的出現,系統的鏈接器 通常不支持C++符號, 和仍然需要mangling。

至於compatibility

爲了讓編譯器廠商 更大的自由度,C++標準 委員會決定不支配 執行名字改編, 異常處理的,和其他 特定於實現的功能。這個決定的缺點是 目標代碼產生的不同 編譯器預計會與 不兼容。然而,有 特定 機器或操作系統的第三方標準 試圖標準化這些平臺上的編譯器(例如C++ ABI [18]);一些編譯器對這些項目採用 二級標準。

http://www.cs.indiana.edu/~welu/notes/node36.html 下面的例子中給出:


例如,對於以下C代碼

int foo(double*); 
double bar(int, double*); 

int foo (double* d) 
{ 
    return 1; 
} 

double bar (int i, double* d) 
{ 
    return 0.9; 
} 

及其符號表將是(由dump -t

[4] 0x18  44  2  1 0 0x2 bar 
[5] 0x0   24  2  1 0 0x2 foo 

對於相同的文件,如果在編譯G ++,則符號表將是

[4] 0x0   24  2  1 0 0x2 _Z3fooPd 
[5] 0x18  44  2  1 0 0x2 _Z3bariPd 

_Z3bariPd指其姓名是杆,其第一arg是整數,第二個參數是指向double的功能。


0

你已經找到了One Definition Rule。很明顯你的程序有一個bug,因爲

  • 一旦程序被鏈接,只能有一個名爲foo的對象。
  • 如果一些源文件,包括所有的頭文件,它會看到的foo兩個定義。

因爲「name mangling」,C++編譯器可以繞過#1:鏈接程序中變量的名稱可能與您選擇的名稱不同。在這種情況下,它不是必需的,但它可能是您的編譯器檢測到問題的方式。 #2,但是,仍然,所以你不能這樣做。

如果你真的想打敗的安全機制,可以禁用重整是這樣的:

extern "C" struct abc foo; 

...等文件...

extern "C" struct foo foo; 

extern "C"指示用C ABI協定鏈接。

+0

呵呵,當然,如別人提到的,你應該只使用一個'union'代替。 – Potatoswatter 2010-01-08 08:31:01