2012-01-01 53 views
3

讓我們頭文件var.h爲什麼C++將在單獨模塊中定義的同名變量放入內存中的相同地址?

#include <iostream> 

class var 
    {public: 
     var() {std::cout << "Creating var at " << this << std::endl; } 
     ~var() {std::cout << "Deleting var at " << this << std::endl; } 
    }; 

和兩個源文件,第一lib.cpp

#include "var.h" 
var A; 

和第二app.cpp

#include "var.h" 

var A; 

int main() 
    {return 0; 
    } 

然後,如果我嘗試編譯他們

g++ -c app.cpp 
g++ -c lib.cpp 
g++ -o app app.o lib.o 

鏈接器返回乘法定義的變量錯誤。但是,如果我編譯到共享庫+主應用程序

g++ -fPIC -c lib.cpp 
g++ --shared -o liblib.so lib.o 
g++ -fPIC -c app.cpp 
g++ -o app -llib -L . app.o 

它鏈接沒有錯誤。然而,程序不能正常工作:

./app 
Creating var at 0x6013c0 
Creating var at 0x6013c0 
Deleting var at 0x6013c0 
Deleting var at 0x6013c0 

所以不同的變量被創建在相同的內存地址!例如,當庫和應用程序期望它們具有不同的值(在這種情況下是對象字段的值)時,它可能會陷入嚴重的麻煩。

if class var做內存分配/刪除valgrind警告訪問最近刪除的塊中的內存。

是的,我知道我可以把static var A;而不是var A;和兩種編譯方式將正常工作。我的問題是:爲什麼不能在不同的庫中使用同名變量(甚至函數?)?圖書館的創作者可能對彼此的名字一無所知,也不會被警告使用static。爲什麼GNU鏈接不會警告這種衝突?

而且,順便說一句,dlload可能會陷入同樣的​​麻煩?

UPD。謝謝大家解釋關於命名空間和外部,我明白了爲什麼相同的符號被放入同一個內存地址,但我仍然無法得到爲什麼沒有鏈接錯誤或甚至警告雙重定義的變量顯示,但在第二種情況下產生錯誤的代碼。

回答

2

不同的庫應該有不同的名稱全球變量和全球功能,否則非常不愉快的事情發生(例如,當dlopen -ing幾次...)。

通常,表現良好的庫在C中使用通用前綴(如gtk),或在C++中使用名稱空間。

並且庫應該最小化全局狀態(在C++中,它可能應該是類內部的靜態數據)。

您也可以使用GCC接受的visibilityfunction attribute

5

我的問題是:爲什麼不能在不同的庫中使用同名變量(甚至函數?) ?

你可以。你失蹤的事情是,聲明

var A;

沒有定義庫中使用的符號A。他們正在定義要導出的符號,以便其他編譯單元可以參考它!

例如如果在app.cpp,你宣佈

extern var A;

這意味着宣佈「Avar型,其他一些編譯單元是要界定和出口的變量」 - 這個修改您的設置,這將使app.cpp明確要求使用lib.cpp導出的名爲A的對象。

您的設置的問題是,您有兩個不同的編譯單元都試圖導出相同的符號A,這會導致衝突。

Why GNU linked doesn't warn about this conflict?

由於GNU無法知道你想A是一個私有變量,以你的編譯單元,除非你告訴GNU,它應該是私人你的編譯單元。這就是static在這種情況下的含義。

+0

哦,我已經得到了有關的extern,它應該通過圖書館不應該工作?但我仍然困惑,爲什麼連接器提供無效的代碼沒有警告畢竟... – Nick 2012-01-01 16:04:43

+0

我不知道。唉,我沒有足夠的共享庫和gcc知識來知道你是否應該以這種方式使用它們(因此,如果你發現奇怪的行爲,它就在你自己的頭上),或者如果這是真的GCC中的錯誤。 – Hurkyl 2012-01-02 06:08:16

4

目前還不清楚您是在詢問這是否應該發生或理由是什麼。

首先,它是必需的行爲。根據C++標準第3.2節的「一個定義規則」,如果多個翻譯單元包含相同的定義(並滿足某些其他要求),那麼該程序的行爲就好像只有一個定義。在有多個定義的其他情況下,行爲是未定義的。

如果你問這個規則的基本原理是什麼,那就是它通常是你想要的。如果多個定義未標記爲extern,則編譯器可能會有一個選項提醒。

+0

只是要清楚,他不符合其他要求,他正在看UB。 – Potatoswatter 2012-01-05 17:27:18

0

有些簡單的答案:「庫」是一個實現細節。所有目標文件被合併(鏈接)至單個單元(可執行)之前以執行。鏈接完成後,沒有任何庫,原始源文件等的痕跡 - 重要的是最終的可執行文件。

現在,你似乎感到驚訝的是,程序中的同一個全局名稱(= linikng everything的最終結果)總是指向同一個對象。如果不是這樣,它會不會令人困惑?

如果file1.cpp和file2.cpp都定義了一個帶有外部鏈接的變量A,那麼編譯器和鏈接器應該如何知道您是否需要一個或兩個不同的對象?更重要的是,人類如何讀取代碼,以確定原作者是否想要創建一個或兩個對象?

+1

你不能*定義兩次相同的對象。不過,你可以*申報兩次。他的程序不合格,因爲'A'應該在兩個翻譯單元中命名同一個對象,並且這兩個單元也定義*'A'。奇怪的是,海灣合作委員會正確地抱怨他的第一個版本,但沒有抱怨他的第二個版本。 – Hurkyl 2012-01-02 06:05:27

+0

那麼,你可以在多個目標文件中擁有'int A;',並且不會有鏈接器錯誤。無論如何,正如我所說的那樣,它有點簡化/不精確的答案。 – zvrba 2012-01-02 07:40:34

+0

Hurkyl是對的,問題是爲什麼gcc在第一種情況下抱怨,而在第二種情況下卻不會產生不正確的代碼。 – Nick 2012-01-13 16:16:00

1

帶有extern鏈接的符號(這是此例中的默認鏈接)對其他翻譯單元可見。這是爲了允許源文件,庫等之間的接口

定義的存在或不存在不會改變訪問哪個對象。程序員負責安排聲明和定義,使得一個對象在使用之前總是被聲明並且總是被定義一次(單定義規則)。

最好的解決方案是將私有全局變量放入未命名的名稱空間,這樣看起來相同的定義仍然可以不同。

lib.cpp

#include "var.h" 
namespace { // unnamed namespace 
    var A; // object inaccessible to other translation units 
} 

app.cpp

#include "var.h" 

namespace { // different unnamed namespace 
    var A; // different object 
} 

int main() 
    {return 0;} 
相關問題