代码在存在时如何运行覆盖不明确的功能?

Xu *_*Hui 5 c++ linker compilation g++ ambiguous

当存在覆盖不明确的函数时,我无法完全理解代码结果。

我有一个库libMy,其中包含两个类AB.

代码显示如下

// A.h
#ifndef included_A_h
#define included_A_h

class A
{
public:
    void print();
};

#endif
Run Code Online (Sandbox Code Playgroud)
// A.cpp
#include "A.h"
#include <iostream>
void A::print()
{
    std::cout << "A from library" << std::endl;
}
Run Code Online (Sandbox Code Playgroud)
// B.h
#ifndef included_B_h
#define included_B_h
class A;
class B
{
public:
    void printA(A &a);
};

#endif
Run Code Online (Sandbox Code Playgroud)
// B.cpp
#include "B.h"
#include "A.h"
void B::printA(A &a)
{
    a.print();
}
Run Code Online (Sandbox Code Playgroud)

我有两个主要功能,它们可以用库生成两个可执行文件。

可以发现 Main*.cpp 看起来很奇怪。为什么需要看起来如此在底部解释。

// MainUsingCPP.cpp
#include <iostream>
#define included_A_h
class A
{
public:
    void print()
    {
        std::cout << "A from Main" << std::endl;
    }
};

#include "B.cpp" // note: using B.cpp here

int main()
{
    A obj_a;
    B obj_b;
    obj_b.printA(obj_a);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)
// MainUsingH.cpp
#include <iostream>
#define included_A_h
class A
{
public:
    void print()
    {
        std::cout << "A from Main" << std::endl;
    }
};

#include "B.h" // note: using B.h here

int main()
{
    A obj_a;
    B obj_b;
    obj_b.printA(obj_a);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

使用下面的行,我们可以编译库,并生成可执行文件。

# generate library
g++ -c A.cpp
g++ -c B.cpp
ar -crv libMy.a A.o B.o

# compile case CPP
g++ MainUsingCPP.cpp -L . -lMy -o MainUsingCPP

# compile case H
g++ MainUsingH.cpp -L . -lMy -o MainUsingH
Run Code Online (Sandbox Code Playgroud)

并运行可执行文件,结果如下

./MainUsingH
A from library
./MainUsingCPP
A from Main
Run Code Online (Sandbox Code Playgroud)

我的问题是:

(1)为什么代码可以编译?

采取MainUsingCPP.cpp和库的考虑,A类被重新定义。所以我们有两个A::print()版本。一个MainUsingCPP.cpp来自图书馆,另一个来自图书馆。在这些阶段,A::print()是模棱两可的。为什么代码可以编译?链接器如何区分它们?链接器如何决定它需要使用哪个版本的函数?

(2)如何理解结果?

为什么导致两个可执行文件不同?为什么链接器A::print()从库中MainUsingH.cpp选择并A::print()从主中选择MainUsingCPP.cpp

为什么 Main.cpp 看起来很奇怪

A是一个类并且BA的用户。在 中MainUsingCPP.cppA的功能似乎可以重新定义。也就是说,A即使 A 没有虚函数,也可以模拟单元测试!

更多可以在fake/mock nonvirtual C++ methods中看到Peter Dotchev的回答

谢谢你的时间!

Use*_*ess 5

(1)为什么代码可以编译?

一个定义规则

在任何一个翻译单元中只允许定义任何变量、函数、类类型、枚举类型、...或模板

这是满足的,因为每个目标文件对应一个不同的翻译单元。

这么多编译(翻译单元到目标文件) - 现在用于链接:

每个非内联函数或变量的一个且只有一个定义……需要出现在整个程序中(包括任何标准和用户定义的库)。编译器不需要诊断这种违反,但违反它的程序的行为是未定义的。

所以你的程序行为是未定义的,编译器不需要告诉你,甚至不需要自己确定。


(2)如何理解结果?

不要试图理解未定义的行为,它是未定义的。

如果你想了解你的特定编译器对你的损坏代码做了什么,那么让它为你扩展两个主要的翻译单元(-E用于 GCC)。但是,这实际上是关于您的编译器而不是语言的问题,因为语言没有明确定义这种情况。