我已经在SO上阅读了有关外部/内部联系的现有问题。我的问题不同 - 如果我在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;
使用Dev-C ++和C程序,上述程序可以完美地编译和链接;如果将其编译为C ++程序,则会产生多重重定义错误。为什么它应该在C下工作?与C ++有什么区别?此行为是否未定义且依赖于编译器?这个代码有多“糟糕”,如果我想重构它,我该怎么办(我遇到过很多像这样编写的旧代码)?
答案 0 :(得分:4)
C和C ++都有一个“一个定义规则”,即每个对象只能在任何程序中定义一次。违反此规则会导致未定义的行为,这意味着您在编译时可能会看到或未看到诊断消息。
文件范围内的以下声明之间存在语言差异,但它并不直接与您的示例有关。
int a;
在C中,这是一个暂定的定义。它可以与同一翻译单元中的其他暂定定义合并,以形成单一定义。在C ++中,它始终是一个定义(您必须使用extern
来声明对象而不定义它),并且同一个转换单元中同一对象的任何后续定义都是错误。
在您的示例中,两个翻译单元的临时定义都有xyz
的(冲突的)定义。
答案 1 :(得分:2)
这是由C ++的名称错误造成的。来自Wikipedia:
第一批C ++编译器是 作为C源的翻译实现 代码,然后由编译 一个C编译器来对象代码;因为 其中,符号名称必须符合 到C标识符规则。甚至以后 随着编译器的出现 生产机器代码或组装 直接,系统的链接器 一般不支持C ++符号, 还需要修剪。
为了给编译器供应商 更大的自由,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
表示名称为bar且第一个arg为整数的函数,第二个参数是指向double的指针。
答案 2 :(得分:1)
C ++不允许多次定义符号。不确定C链接器正在做什么,一个好的猜测可能是它只是将两个定义映射到同一个符号上,这当然会导致严重的错误。
对于移植,我会尝试将各个C文件的内容放入匿名命名空间,这实际上使符号不同,并且文件是本地的,因此它们不会在其他地方与同一名称冲突。
答案 3 :(得分:0)
C程序允许这样做并将内存视为联盟。它会运行,但可能无法满足您的预期。
C ++程序(更强类型)正确检测到问题并要求您修复它。如果你真的想要一个联盟,请将其声明为一个联盟。如果您想要两个不同的对象,请限制它们的范围。
答案 4 :(得分:0)
您找到了One Definition Rule。很明显,你的程序有一个错误,因为
foo
的对象。foo
。由于“名称修改”,C ++编译器可以绕过#1:链接程序中变量的名称可能与您选择的名称不同。在这种情况下,它不是必需的,但它可能是您的编译器检测到问题的方式。但是,#2仍然存在,所以你不能这样做。
如果您真的想要破坏安全机制,可以禁用这样的修改:
extern "C" struct abc foo;
......其他档案......
extern "C" struct foo foo;
extern "C"
指示链接器使用C ABI约定。