案例一:一个函数的多个定义
模块1.cpp:
void f(){}
主要.cpp:
void f(){} // error LNK2005: "void __cdecl f(void)" (?func@@YAXXZ) already defined in module1.obj
int main(){}
案例二:一个类的多个定义
模块1.cpp:
class C{};
主要.cpp:
class C{}; // OK
int main(){}
在情况 1 中,正如预期的那样,(Microsoft) 链接器遇到同一函数的两个定义并发出错误。在情况 2 中,它允许同一类的两个定义。
问题 1:为什么链接器在同一类有多个定义时不报错?这是否与函数名称只是其指令开始和类的地址名称有关name 是新类型的名称?
此外,即使我们使用类的不同定义,链接器也不会报错(我添加了调用类构造函数的函数,因此它们出现在符号表中):
模块1.cpp:
class MyClass
{
int n;
public:
MyClass() : n(123){}
};
void func()
{
MyClass c;
}
主要.cpp:
class MyClass
{
float n;
public:
MyClass() : n(3.14f){}
};
int main()
{
MyClass c;
}
我设置了编译器选项,以便它生成 COD
文件和 OBJ
文件。我可以看到两个构造函数都出现在相同的损坏名称下 (??0MyClass@@QAE@XZ
),每个都在其自己的单元(COD
文件)中。预计如果某个符号在模块中被引用,链接器将使用来自同一模块的定义(如果存在)。如果不是,它将使用定义它的模块中的符号定义。这可能很危险,因为链接器似乎从它遇到的第一个目标文件中选择符号:
模块1.h:
#ifndef MODULE1_H_
#define MODULE1_H_
void func1();
#endif
模块1.cpp:
#include <iostream>
#include "module1.h"
class MyClass
{
int myValue;
public:
MyClass() : myValue(123)
{
std::cout << "MyClass::MyClass() [module1]" << std::endl;
}
void foo()
{
std::cout << "MyClass::foo() [module1]: n = " << myValue << std::endl;
}
};
void func1()
{
MyClass c;
c.foo();
}
模块2.cpp:
#include <iostream>
class MyClass
{
public:
MyClass()
{
std::cout << "MyClass::MyClass() [module2]" << std::endl;
}
};
// it is necessary that module contains at least one function that creates MyClass object
void test2()
{
MyClass c;
}
主要.cpp:
#include "module1.h"
int main()
{
func1();
}
如果目标文件在传递给链接器时按此顺序列出:
module2.obj module1.obj main.obj
链接器将从第一个 obj 文件中选择构造函数 MyClass::MyClass
而从第二个文件中选择 MyClass::foo
所以输出是意外的(错误的):
MyClass::MyClass() [module2]
MyClass::foo() [module1]: n = 1
如果目标文件在传递给链接器时按此顺序列出:
module1.obj module2.obj main.obj
链接器将从第一个 obj 文件中选取两个 MyClass
成员:
MyClass::MyClass() [module1]
MyClass::foo() [module1]: n = 123
问题 2:为什么链接器的设计方式允许多个类定义,这会导致上述错误?链接过程取决于目标文件的顺序是不是错了?
似乎链接器选择了它在扫描目标文件时遇到的第一个符号定义,然后默默地丢弃所有后续定义重复项。
问题 3:链接器就是这样构建其符号查找表的吗?
最佳答案
关于您的问题 1:类和内联函数的多个定义是允许的,只要您不违反单一定义规则 (ODR)。
当您在类中定义一个函数时,它是隐式内联
。您通过使用 MyClass
的构造函数违反 ODR 调用了未定义的行为。
这种行为的基本原理是:当类中有一个内联函数时,它在许多编译单元中都是可见的,显然没有编译单元是“首选”编译单元。但是,您的工具链可以依赖 ODR 并假设所有内联方法都具有相同的语义。因此,链接器可以在链接中选择任何内联函数定义,因为它们都是相同的。
问题的解决方案很简单:不要违反 ODR。
关于c++ - 为什么链接器允许多个符号定义?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10653174/