为什么c ++将在单独模块中定义的同名变量放入内存中的相同地址?

时间:2012-01-01 12:55:41

标签: c++ g++

我们来看头文件var.h

#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

所以在同一个内存地址创建了不同的变量!例如,在库和应用程序期望它们具有不同的值(在这种情况下为对象字段的值)的情况下,它可能会遇到严重的麻烦。

如果class var进行内存分配/删除,则valgrind会警告访问最近删除的块中的内存。

是的,我知道我可以放static var A;而不是var A;,两种编译方式都能正常运行。我的问题是:为什么不能在不同的库中使用同名变量(甚至函数?)?图书馆创建者可能对彼此使用的名称一无所知,也不会被警告使用static。为什么GNU链接不会警告这种冲突?

而且,BTW,dlload会遇到同样的麻烦吗?

UPD。谢谢大家解释有关命名空间和extern的问题,我看到为什么相同的符号被放入相同的内存地址,但我仍然无法理解为什么没有显示链接错误甚至是关于双重定义变量的警告,而是在第二种情况下产生了错误的代码。

5 个答案:

答案 0 :(得分:5)

  

我的问题是:为什么一个人不能使用同名的变量(甚至是函数?)   在不同的图书馆?

你可以。你缺少的是声明

var A;

未定义用于库中的符号A。他们定义了要导出的符号,以便任何其他编译单元可以引用它!

e.g。如果,在app.cpp中,您声明了

extern var A;

这意味着声明“Avar类型的变量,其他一些编译单元将定义并导出” - 通过对您的设置进行此修改,这将使{{1显式请求使用app.cpp导出的名为A的对象。

您的设置问题是您有两个不同的编译单元都试图导出相同的符号lib.cpp,这会导致冲突。

Why GNU linked doesn't warn about this conflict?

因为GNU无法知道你希望A成为编译单元的私有变量,除非你告诉 GNU它应该是你的编译单元私有的。这就是A在这种情况下的含义。

答案 1 :(得分:4)

目前尚不清楚你是否应该问这是否应该发生或者理由是什么。

首先,这是必需的行为。根据“一个定义规则”,即C ++标准的第3.2节,如果多个翻译单元包含相同的定义(并且满足某些其他要求),则程序应该表现得好像存在单个定义。在存在多个定义的任何其他情况下,行为未定义。

如果你问这个规则的基本原理是什么,那就是你通常想要的。如果多个定义未标记为extern,您的编译器可能会提醒您。

答案 2 :(得分:2)

不同的库应该具有不同名称的全局变量和全局函数,否则会发生非常不愉快的事情(例如,dlopen多次... )。

传统上,表现良好的库在C中使用公共前缀(如gtk),或在C ++中使用名称空间。

库应该最小化全局状态(在C ++中,它可能应该是类中的静态数据)。

您还可以使用GCC接受的visibility function attribute

答案 3 :(得分: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;}

答案 4 :(得分:0)

有些简化的答案:&#34;图书馆&#34;是一个实现细节。所有目标文件组合(链接)到单元(可执行)之前执行。链接完成后,不再有库,原始源文件等的痕迹 - 重要的是最终的可执行文件。

现在,您似乎对程序中相同的全局名称(= linikng所有内容的最终结果)始终引用相同的对象感到惊讶。如果不是这样的话会不会让人感到困惑?

如果file1.cpp和file2.cpp都定义了带有外部链接的变量A,那么编译器和链接器应该如何知道您是否需要一个或两个不同的对象?更重要的是,阅读代码的 human 如何知道原作者是否想要创建一个或两个对象?