具有模板的对象文件如何链接在一起
How object files with templates are linked together
假设我们有三个.h
文件:
f.h
:
template <typename T> class Class {public: Class() {} T id(T x) { return x; }};
g.h
:
template <typename T> class Class {public: Class() {} T id(T x) { return x + 100; }};
h.h
:
template <typename T> class Class {public: Class(); T id(T x); };
现在,我们也有三个.cpp
文件:
f.cpp
:
#include "f.h"
int f(int x) { Class<int> t; return t.id(x); }
g.cpp
:
#include "g.h"
int g(int x) { Class<int> t; return t.id(x); }
h.cpp
:
#include "h.h"
int h(int x) { Class<int> t; return t.id(x); }
编译得到f.o
, g.o
和h.o
。现在让我们加入main.cpp
:
#include <stdio>
extern int f(int);
extern int g(int);
extern int h(int);
int main() {
std::cout << f(1) << std::endl;
std::cout << g(2) << std::endl;
std::cout << h(3) << std::endl;
}
a-a -我们来做g++ main.cpp f.o g.o h.o
。现在我真正感到惊讶的是:由于这三个.o
文件包含int Class<int>::id(int)
的三个不同定义,我预计会得到一个链接错误。然而,我得到的是一个工作的a.out
,它打印1 2 3
。如果我在命令中重新排序.o
文件,它将打印101 102 103
。
现在是实际问题:在这种情况下,链接器究竟是如何执行链接的?它如何确定Class<int>
的哪些实例化要保留,哪些要丢弃?为什么它不抱怨多重定义?
nm
实用程序给出以下nm f.o g.o h.o
的输出:
f.o:
00000000 b .bss
00000000 d .data
00000000 t .text
00000000 t .text$_ZN5ClassIiE2idEi
00000000 t .text$_ZN5ClassIiEC1Ev
00000000 T __Z1fi
00000000 T __ZN5ClassIiE2idEi
00000000 T __ZN5ClassIiEC1Ev
g.o:
00000000 b .bss
00000000 d .data
00000000 t .text
00000000 t .text$_ZN5ClassIiE2idEi
00000000 t .text$_ZN5ClassIiEC1Ev
00000000 T __Z1gi
00000000 T __ZN5ClassIiE2idEi
00000000 T __ZN5ClassIiEC1Ev
h.o:
00000000 b .bss
00000000 d .data
00000000 d .eh_frame
00000000 t .text
00000000 T __Z1hi
U __ZN5ClassIiE2idEi
U __ZN5ClassIiEC1Ev
显然,f.o
和g.o
都导出了符号__ZN5ClassIiE2idEi
, h.o
导入了该符号(大写字母表示外部链接)。它不会导致错误。为什么?
这个问题实际上是众所周知的:您违反了ODR。由于模板的性质,编译器和链接器不会注意到这一点。结果如下:
链接器假设没有违反ODR,因此可以自由地使用模板的任何实例化(即。具有相同参数的模板的所有实例化都会生成完全相同的代码)。这个系统是有意义的,只要你不违反ODR。
现在,在您的示例中,链接器选择使用它获得的第一个实例化,这导致结果依赖于链接的顺序。
听起来太明显了,但是您的类正在使用隐式内联。也就是说,当您将源代码放入类声明中时,您是在"暗示"您希望将其内联。所以…
链接器只能看到f(),g()和h()。
用这样的东西来重试你的代码可能会很有趣,尽管它仍然很简单,编译器可能会简单地内联它(取决于你的编译器)。
template <typename T> class Class
{
public:
Class() {}
T id(T x);
};
template <typename T>
T Class::id(T x)
{
return x;
}
- lambda参数转换为constexpr技巧,然后获取带链接的数组
- CMake-按正确顺序将项目与C运行时对象文件链接
- 从链接列表c++中删除一个项目
- 有根的二进制搜索树.保留与其父级的链接
- 读取文件的最后一行并输入到链接列表时出错
- 静态数据成员的问题-修复链接错误会导致编译器错误
- node-gyp 在 macOS 上未正确链接库
- 基于boost的程序的静态链接——zlib问题
- 无法链接 CMake 中的本地库
- 内联函数中具有内部链接的全局变量
- 链接阶段在Ubuntu上失败,但在MacOS上失败
- 在链接链接静态(GSOAP)库的共享库时,为什么会得到一个未定义的符号
- 预制链接链接到共享库不起作用
- 未定义的参考,但链接链接
- 链接链接器/加载器错误时"undefined reference to ..."与拉斯皮康库链接时
- 多次使用SetWindowsHookEx取消挂接挂接集会导致挂接进程崩溃
- 将Typedef语句放入类链接链接列表中的C 错误
- GTEST测试项目链接链接到其他可执行文件
- 链接到链接到另一个共享库的共享库会在退出时出错
- 链接到链接到库的库