2012-01-01 51 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;} 
相关问题