c++ - 为什么链接器允许多个符号定义?

标签 c++ visual-c++ linker

案例一:一个函数的多个定义

模块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/

相关文章:

c++ - 在编译时是否需要短路评估规则?

c# - 将对象从 C# COM 库传递到 C++ 应用程序时如何修复内存泄漏

c++ - size_t vs int 警告

rust - build.rs 找不到 native 静态库 'libvmaf,可能缺少 -L 标志?

gcc - 链接的 ELF 二进制文件可以包含多个 .text.* 部分(自动)

c++ - 如何访问具有多个括号的一维数组以提高可读性?

c++ - 多态工厂

c++ - 通过提高新 C++ 代码的警告级别来强制构建的好方法

eclipse - 如何将 Armadillo 与 Eclipse 链接

c++ - 在 Windows (Vista) 上使用 C++、Qt 的 GUI 设计问题