对于多个C++编译单元,总是先编译器独立编译各个.cpp生成对应到目标文件.o或者.obj,然后再由链接器将各个目标文件进行链接从而得到最终的可执行文件(PE or ELF)。其实,.o和.obj和最终的可执行文件的格式已经是一样的了,只是其内部可能还有未解析的符号,而且地址也还是相对地址,链接时对未解析的外部符号进行地址替换,同时进行地址重定位,生成虚拟地址空间的可执行二进制代码文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// test.h
void f(int);
// test.cpp
#include "test.h"
void f(int n)
{
	// do sthg.
}
// main.cpp
#include "test.h"
int
main()
{
	f(1);
	f(10);
	return 0;
}
1
2
$ g++ test.cpp main.cpp -c
$ g++ test.o main.o -o main

  这里,编译main.cpp生成main.o时发现无法找到f到定义,就把它当作外部符号填入外部符号表,等待链接时确定其地址。而f(1); f(10); 通常是对应两条jmp 0x****h指令(当然还有参数的压栈操作),0x****h地址处通常就是一条call f; (C++还会对f进行mangling处理)。这样链接器在链接main.o和test.o时,从test.o到导出符号表(或者内部符号表?)中找到f,得到对应的地址(每个符号对应一个偏移地址),用这个地址替换f,再进行其他的一些必要的重定位,一个可执行文件就形成了。
  说了这么多,那么C++模板为什么不能分离式编译呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// template.h
template <class T>
void f(T t);
// template.cpp
#include "template.h"
template <class T>
void f(T t)
{
	// do sthg.
	T tt;
}
// main.cpp
#include "template.h"
int
main()
{
	f(10);
	f(1.0);
	return 0;
}
1
2
$ g++ template.cpp main.cpp -c
$ g++ template.o main.o -o main

  这里,g++会提示:

main.o: In function `main':
main.cpp:(.text+0x11): undefined reference to `void f(int)'

1
$ readelf template.o -d

反汇编也没有任何输出,说明template.o的.text段中一行代码都没有生成。问题就在这里,因为只有当模板函数被调用时编译器才会其实例化,为其生成代码。又由于template.cpp是独立编译的,它看不到main.cpp对f(T t)的调用,因此没有实例化的f(int)代码生成。如果将template.cpp加入template.h,f(10)和f(1.0)就会分别被实例化为f(int)和f(double),一切都恢复正常。

over~~~

???我写这些干吗?God knows.
Bayes theorem 真的很牛B,很神奇。
2 \pi r \to \pi r^2, en\,effect!?

Tags: ,.
你好!除了代码,此处没有多少原创之物,皆为本人搜集、整理、总结之记录与心得,欢迎转载分享!转载时请尽量注明出处,将不胜感激。祝你健康、快乐!
Home

RFC: Request For Comments. Orz..

Name(required)
Mail (required),(will not be published)

RFC: Request For Comments. Orz..

Website(recommended)