如果两个C ++文件具有相同名称的类的不同定义,则在编译和链接时,即使没有警告,也会抛出某些内容。例如,
// a.cc
class Student {
public:
std::string foo() { return "A"; }
};
void foo_a()
{
Student stu;
std::cout << stu.foo() << std::endl;
}
// b.cc
class Student {
public:
std::string foo() { return "B"; }
};
void foo_b()
{
Student stu;
std::cout << stu.foo() << std::endl;
}
使用g ++编译和链接在一起时,两者都将输出“A”(如果a.cc在命令行顺序中位于b.cc之前)。
类似的主题是here。我看到命名空间将解决这个问题,但我不知道为什么链接器甚至不会发出警告。如果该类的一个定义具有未在另一个中定义的额外函数,则说b.cc更新为:
// b.cc
class Student {
public:
std::string foo() { return "B"; }
std::string bar() { return "K"; }
};
void foo_b()
{
Student stu;
std::cout << stu.foo() << stu.bar() << std::endl;
}
然后stu.bar()效果很好。感谢任何能告诉我编译器和链接器如何在这种情况下工作的人。
作为一个额外的问题,如果在头文件中定义了类,它们是否应始终用未命名的命名空间包装以避免这种情况?有副作用吗?
答案 0 :(得分:19)
这违反了一个定义规则(C ++ 03,3.2 / 5“一个定义规则”),它说(除其他外):
类类型可以有多个定义(第9条),...... 在一个程序中,每个定义都以不同的形式出现 翻译单元,并提供满足以下要求的定义 要求。鉴于这样一个名为D的实体在多个中定义 翻译单位,然后
- D的每个定义应由相同的令牌序列组成;
如果违反了一个定义规则,则行为未定义(这意味着可能会发生奇怪的事情)。
链接器看到Student::foo()
的多个定义 - 一个在对象文件中,一个在b中。然而它并没有抱怨这个;它只选择两个中的一个(碰巧,它遇到的第一个)。重复函数的这种“软”处理显然只发生在内联函数中。对于非内联函数,链接器将抱怨多个定义,并拒绝生成可执行文件(可能存在放宽此限制的选项)。 GNU ld
和MSVC的链接器都以这种方式运行。
行为有一定道理;内联函数需要在它们所使用的每个翻译单元中可用。并且在一般情况下,它们需要具有非内联版本(如果调用未内联或者函数的地址被采用)。 inline
实际上只是围绕单定义规则的免费传递 - 但要使其工作,所有内联定义都必须相同。
当我查看目标文件的转储时,我没有看到任何明显的东西向我解释链接器如何知道一个函数被允许有多个定义而其他函数没有,但我确定有一些标志或记录这样做。不幸的是,我发现链接器的工作方式和目标文件的详细信息没有特别详细记录,因此精确的机制对我来说可能仍然是一个谜。
至于你的第二个问题:
作为一个额外的问题,如果在头文件中定义类,应该 它们总是用未命名的命名空间包装以避免这种情况? 有副作用吗?
你几乎肯定不想这样做,每个类在每个翻译单元中都是一个不同的类型,所以从技术上讲,类的实例不能从一个翻译单元传递到另一个翻译单元(通过指针,引用或复制)。此外,您最终会得到任何静态成员的多个实例。这可能不会很好。
将它们放在不同的命名空间中。
答案 1 :(得分:3)
您违反了类定义的一个定义规则,并且该语言专门禁止这样做。编译器/链接器不需要警告或诊断,并且在这种情况下肯定不能保证这种情况按预期工作。
答案 2 :(得分:2)
但我不知道为什么链接器甚至不会发出警告。
违反单一定义规则不需要诊断,因为为了实现诊断,链接器必须证明这两个定义实际上并不相同。
一旦类具有不同的大小,不同数量的成员或不同的基类,这很容易。但是,只要所有这些因素相同,您就可以仍然拥有不同的成员定义,并且链接器必须使用一些高级内省来比较它们。即使这总是可能的(我不确定它是什么,因为目标文件可以用不同的选项编译,导致不同的输出),它的效率肯定非常低。
因此,链接器只接受您向其投掷的内容不会违反ODR。不是一个完美的情况,但很好。
答案 3 :(得分:0)
我认为你的“额外”问题是主要问题的线索。
如果我理解你的额外问题,那么我认为你不想要将它们包装在命名空间中,因为如果你将同一个类#include到多个.cc文件中,那么你可能想要只使用每个方法的一个副本,即使它们是在类中定义的。
这(有点)解释了为什么在主示例中只能获得每个函数的一个版本。我希望链接器只是假设两个函数是相同的 - 从相同的#included源生成。如果链接器会检测到它们何时不同并发出警告会很好,但我想这很难。
正如Mark B所说,实际答案是“不要去那里”。
答案 4 :(得分:0)
除了违反一个定义规则外,由于Name mangling in C++
,您看不到编译器抱怨编辑: 正如Konrad Rudolph所指出的那样:在这种情况下,混淆的名字会是相同的。