假设我们有三个.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;
}
啊,让我们做吧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
导入这个符号(大写字母表示外部链接)。它不会导致任何错误。为什么?