9

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

4

2 回答 2

3

这个问题实际上是众所周知的:您违反了 ODR。由于模板的性质,编译器和链接器不会注意到这一点。会发生以下情况:

链接器假定ODR 没有被违反,因此可以自由使用模板的任何实例化(又名。具有相同参数的模板的所有实例化都会生成完全相同的代码)。只要您不违反 ODR,该系统就有意义。

现在,在您的情况下,链接器选择使用它获得的第一个实例化,这导致结果取决于链接的顺序。

于 2013-01-22T13:11:11.300 回答
0

听起来太明显了,但是您的班级正在使用隐式内联。也就是说,当您将源代码放在类声明中时,您就是在“暗示”您希望它内联。所以...

链接器将看到的唯一内容是 f()、g() 和 h()。

用这样的东西重试你的代码可能会很有趣,尽管它仍然很简单,编译器可能仍然只是内联它(取决于你的编译器)。

template <typename T> class Class 
{
public: 
     Class() {}
T id(T x);
};

template <typename T> 
T Class::id(T x)
{ 
   return x; 
}
于 2013-01-22T14:15:33.743 回答