接着上一篇文章讲,为什么单文件会编译失败。
上一篇文章:【C++】template方法undefined reference to
一个c/c++文件要经过预处理、编译、汇编和链接才能变成可执行文件。接下来,我们用 gcc(g++) 完成这个过程。
预处理就是将要包含(include)的文件插入原文件中、将宏定义展开、根据条件编译命令选择要使用的代码,最后将这些东西输出到一个“.i”文件中等待进一步处理。这个过程是不检查语法错误的,相当于只进行了替换。
gcc 的预处理命令为
g++ -E 源文件
我们将这次的两个源文件分别预处理,目标文件拥有相同的文件名,但扩展名改为.i:
g++ -E -o test.i test.cpp
g++ -E -o templateFunc.i templateFunc.cpp
template <typename T>
T addTwo(T &a, T &b);
#include "templateFunc.h"
template <typename T>
T addTwo(T &a, T &b)
{
return a + b;
}
# 0 "templateFunc.cpp"
# 0 ""
# 0 ""
# 1 "templateFunc.cpp"
# 1 "templateFunc.h" 1
template <typename T>
T addTwo(T &a, T &b);
# 2 "templateFunc.cpp" 2
template <typename T>
T addTwo(T &a, T &b)
{
return a + b;
}
显然只是把头文件在源文件里展开了。
另一个test.cpp由于include了iostream这个大库,总文件行数达到了 34000 多行… …

编译就是把C/C++代码(比如上述的“.i”文件)“翻译”成汇编代码。这一步如果有语法错误会检查出来的。
gcc的编译命令为:
g++ -S
此次将输出的文件,扩展名为.s
g++ -S -o test.s test.i
g++ -S -o templateFunc.s templateFunc.i
这一步输出的文件使用汇编语言,我看不懂,就不解释了

汇编就是将第二步输出的汇编代码翻译成符合一定格式的机器代码,输出的文件为.o文件
gcc的汇编命令为:
g++ -c
分别将两个编译完的.s文件汇编为.o文件
g++ -c -o test.o test.s
g++ -c -o templateFunc.o templateFunc.s
机器代码就没法用编辑器看了,我们直接进行下一步吧。
链接就是将上步生成的OBJ文件和系统库的OBJ文件、库文件链接起来,最终生成了可以在特定平台运行的可执行文件。
gcc的链接命令为:
g++ -o
我们将两个.o文件链接为一个可执行文件
g++ -o test test.o templateFunc.o
然后又报了一个熟悉的错误:
C:/tools/mingw-w64-gcc-13.2-stable-r40/bin/../lib/gcc/i686-w64-mingw32/13.2.0/../../../../i686-w64-mingw32/bin/ld.exe: test.o:test.cpp:(.text+0x5b): undefined reference to `int addTwo<int>(int&, int&)'
C:/tools/mingw-w64-gcc-13.2-stable-r40/bin/../lib/gcc/i686-w64-mingw32/13.2.0/../../../../i686-w64-mingw32/bin/ld.exe: test.o:test.cpp:(.text+0xa1): undefined reference to `double addTwo<double>(double&, double&)'
collect2.exe: error: ld returned 1 exit status
我们将模板实例化的代码加入源文件:
template int addTwo<int>(int &a, int &b);
template double addTwo<double>(double &a, double &b);
同样执行上面的流程,可以发现,两次生成的汇编代码完全不一样:
.file "templateFunc.cpp"
.text
.ident "GCC: (GNU) 13.2.0"
.file "templateFunc.cpp"
.text
.section .text$_Z6addTwoIiET_RS0_S1_,"x"
.linkonce discard
.globl __Z6addTwoIiET_RS0_S1_
.def __Z6addTwoIiET_RS0_S1_; .scl 2; .type 32; .endef
__Z6addTwoIiET_RS0_S1_:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax
movl (%eax), %edx
movl 12(%ebp), %eax
movl (%eax), %eax
addl %edx, %eax
popl %ebp
ret
.section .text$_Z6addTwoIdET_RS0_S1_,"x"
.linkonce discard
.globl __Z6addTwoIdET_RS0_S1_
.def __Z6addTwoIdET_RS0_S1_; .scl 2; .type 32; .endef
__Z6addTwoIdET_RS0_S1_:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax
fldl (%eax)
movl 12(%ebp), %eax
fldl (%eax)
faddp %st, %st(1)
popl %ebp
ret
.ident "GCC: (GNU) 13.2.0"
而实例化的代码,最后成功链接成了可执行文件。

所以可以确认两件事: