C和C++之间的链接差异?

问题描述:

我已阅读关于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++有什么区别?这种行为是未定义的还是编译器依赖的?这段代码有多“坏”,如果我想重构它,我该怎么做(我遇到过很多这样写的旧代码)?

C和C++都有一个“一个定义规则”,即每个对象只能在任何程序中定义一次。这条规则的违反导致未定义行为这意味着你可能会或可能不会进行编译时看到诊断信息。

有文件范围内下面的声明之间的语言差异,但它并不直接与你的榜样关心的问题。

int a; 

在C这是一个试探性的定义。它可能与同一翻译单元中的其他暂定义相结合以形成单一定义。在C++中它始终是一个定义(你必须使用extern来声明对象而不定义它),并在相同的翻译单元相同的对象的任何后续定义一个错误。

在您的例子都翻译单元有一个(有冲突)的xyz从他们的初步定义的定义。

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

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

这是由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的功能。


你已经找到了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