
在前面几篇博客
中 , 函数模板 可以与 重载的 普通函数 放在一起 , 二者之间 的调用 有 不同的优先级 ;
在一定程度上 , 说明 函数模板 和 普通函数 有着相似性 ,
在本篇博客中 分析 C++ 编译器的 函数模板 实现底层机制 ;
gcc 编译器 英文名称是 " GNU C Compiler " ,
参考 【C 语言】编译过程 分析 ( 预处理 | 编译 | 汇编 | 链接 | 宏定义 | 条件编译 | 编译器指示字 ) 博客 , C 语言 程序的编译 需要经过 预处理 , 编译 , 汇编 , 链接 操作 , 分别需要使用 预处理器 , 编译器 , 汇编器 , 链接器 四个工具 ;

集成开发环境 将 预处理器 , 编译器 , 汇编器 , 链接器 四个工具 集成到了一起 ;
打开 Visual Studio 中解决方案 所在目录 , 其中就有 编译过程 中产生的大量的 中间文件 ;

预处理 Pre-Processing : 展开 宏定义 , 得到预处理文件 ;
gcc Test.c -o Test.i也可以加上 -E 选项 ;
gcc -E Test.c -o Test.i编译 Compiling : 将预处理文件编译成 汇编文件 ;
gcc Test.i -o Test.S直接从 Test.c 源码生成 汇编文件 :
gcc -S Test.c -o Test.S汇编 Assembling : 将 汇编文件 编译成 二进制机器码文件 ;
gcc Test.S -o Test.o直接从 Test.c 源码生成 机器码文件 :
gcc -c Test.c -o Test.o链接 Linking : 将 二进制机器码文件 链接成 可执行文件 ;
gcc Test.o -o Test.exe直接生成可执行文件 :
gcc Test.cgcc Test.c -o Test.exe编译 C++ 代码 , 将 gcc 改为 g++ 即可 ;
gcc 编译器 与 g++ 编译器 的区别如下 :
gcc / g++ 编译器常用命令选项 :
在 Test.c 中定义一个简单 函数模板 , 然后再 main 函数中调用该 函数模板 ,
#include "iostream"
using namespace std;
template <typename T>
T add(T a, T b) {
cout << "调用函数模板 T add(T a, T b)" << endl;
return a + b;
}
int main() {
int a = 10, b = 20;
int c = add(a, b);
cout << "函数模板计算结果 : c = " << c << endl;
return 0;
}执行
g++ -S Test.cpp -o Test.S命令 , 生成 该 C++ 源码对应的汇编文件 ;

生成的汇编文件 Test.S 内容如下 :
.file "Test.cpp"
.lcomm __ZStL8__ioinit,1,1
.def ___main; .scl 2; .type 32; .endef
.section .rdata,"dr"
.align 4
LC0:
.ascii "\345\207\275\346\225\260\346\250\241\346\235\277\350\256\241\347\256\227\347\273\223\346\236\234 : c = \0"
.text
.globl _main
.def _main; .scl 2; .type 32; .endef
_main:
leal 4(%esp), %ecx
andl $-16, %esp
pushl -4(%ecx)
pushl %ebp
movl %esp, %ebp
pushl %ecx
subl $36, %esp
call ___main
movl $10, -12(%ebp)
movl $20, -16(%ebp)
movl -16(%ebp), %eax
movl %eax, 4(%esp)
movl -12(%ebp), %eax
movl %eax, (%esp)
call __Z3addIiET_S0_S0_
movl %eax, -20(%ebp)
movl $LC0, 4(%esp)
movl $__ZSt4cout, (%esp)
call __ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
movl %eax, %edx
movl -20(%ebp), %eax
movl %eax, (%esp)
movl %edx, %ecx
call __ZNSolsEi
subl $4, %esp
movl $__ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, (%esp)
movl %eax, %ecx
call __ZNSolsEPFRSoS_E
subl $4, %esp
movl $0, %eax
movl -4(%ebp), %ecx
leave
leal -4(%ecx), %esp
ret
.section .rdata,"dr"
.align 4
LC1:
.ascii "\350\260\203\347\224\250\345\207\275\346\225\260\346\250\241\346\235\277 T add(T a, T b)\0"
.section .text$_Z3addIiET_S0_S0_,"x"
.linkonce discard
.globl __Z3addIiET_S0_S0_
.def __Z3addIiET_S0_S0_; .scl 2; .type 32; .endef
__Z3addIiET_S0_S0_:
pushl %ebp
movl %esp, %ebp
subl $24, %esp
movl $LC1, 4(%esp)
movl $__ZSt4cout, (%esp)
call __ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
movl $__ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, (%esp)
movl %eax, %ecx
call __ZNSolsEPFRSoS_E
subl $4, %esp
movl 8(%ebp), %edx
movl 12(%ebp), %eax
addl %edx, %eax
leave
ret
.text
.def ___tcf_0; .scl 3; .type 32; .endef
___tcf_0:
pushl %ebp
movl %esp, %ebp
subl $8, %esp
movl $__ZStL8__ioinit, %ecx
call __ZNSt8ios_base4InitD1Ev
leave
ret
.def __Z41__static_initialization_and_destruction_0ii; .scl 3; .type 32; .endef
__Z41__static_initialization_and_destruction_0ii:
pushl %ebp
movl %esp, %ebp
subl $24, %esp
cmpl $1, 8(%ebp)
jne L6
cmpl $65535, 12(%ebp)
jne L6
movl $__ZStL8__ioinit, %ecx
call __ZNSt8ios_base4InitC1Ev
movl $___tcf_0, (%esp)
call _atexit
L6:
leave
ret
.def __GLOBAL__sub_I_main; .scl 3; .type 32; .endef
__GLOBAL__sub_I_main:
pushl %ebp
movl %esp, %ebp
subl $24, %esp
movl $65535, 4(%esp)
movl $1, (%esp)
call __Z41__static_initialization_and_destruction_0ii
leave
ret
.section .ctors,"w"
.align 4
.long __GLOBAL__sub_I_main
.ident "GCC: (i686-posix-sjlj, built by strawberryperl.com project) 4.9.2"
.def __ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc; .scl 2; .type 32; .endef
.def __ZNSolsEi; .scl 2; .type 32; .endef
.def __ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_; .scl 2; .type 32; .endef
.def __ZNSolsEPFRSoS_E; .scl 2; .type 32; .endef
.def __ZNSt8ios_base4InitD1Ev; .scl 2; .type 32; .endef
.def __ZNSt8ios_base4InitC1Ev; .scl 2; .type 32; .endef
.def _atexit; .scl 2; .type 32; .endef.file "Test.cpp" 表示这是 Test.cpp 源码的 汇编文件 ;
.text 表示 下面是代码 ;
_main: 表示 后面是 main 函数 ;
call __Z3addIiET_S0_S0_ 调用的是 函数模板 , 下面看函数模板的 汇编内容 :
函数模板 的 函数声明 对应的汇编如下 :
LC1:
.ascii "\350\260\203\347\224\250\345\207\275\346\225\260\346\250\241\346\235\277 T add(T a, T b)\0"
.section .text$_Z3addIiET_S0_S0_,"x"
.linkonce discard
.globl __Z3addIiET_S0_S0_
.def __Z3addIiET_S0_S0_; .scl 2; .type 32; .endef这是一个模板函数的汇编版本,函数名为add,它接受两个参数,都是int类型(T在上下文中可以推断为int)。
.ascii "\350\260\203\347\224\250\345\207\275\346\225\260\346\250\241\346\235\277 T add(T a, T b)\0" 这行代码是一个ASCII字符串,它表示函数模板的名称和一些模板参数。这个字符串在汇编代码中可能不会直接出现,而是由编译器插入的。.linkonce discard 这个指示告诉链接器,如果该文件在其他地方被链接了,就丢弃重复的代码。这是一种优化手段,可以避免在最终的可执行文件中包含重复的代码。.globl __Z3addIiET_S0_S0_ 这行代码声明了全局符号__Z3addIiET_S0_S0_。在C++中,编译器会为每个模板函数生成一个特定的符号名称,这是模板函数的实例化。.def __Z3addIiET_S0_S0_; .scl 2; .type 32; .endef 这行代码定义了符号__Z3addIiET_S0_S0_,并设置了一些属性。这些属性可能是由链接器或其他工具使用的,以确定如何处理该符号。函数模板 的 函数体内容 回应的汇编如下 :
__Z3addIiET_S0_S0_:
pushl %ebp
movl %esp, %ebp
subl $24, %esp
movl $LC1, 4(%esp)
movl $__ZSt4cout, (%esp) # 开始打印日志
call __ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
movl $__ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, (%esp)
movl %eax, %ecx
call __ZNSolsEPFRSoS_E # 打印日志结束
subl $4, %esp
movl 8(%ebp), %edx
movl 12(%ebp), %eax
addl %edx, %eax
leave
ret
.text
.def ___tcf_0; .scl 3; .type 32; .endef对应的 C++ 代码如下 :
template <typename T>
T add(T a, T b) {
cout << "调用函数模板 T add(T a, T b)" << endl;
return a + b;
}打印日志
cout << "调用函数模板 T add(T a, T b)" << endl;对应的汇编内容 :
movl $__ZSt4cout, (%esp) # 开始打印日志
call __ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
movl $__ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, (%esp)
movl %eax, %ecx
call __ZNSolsEPFRSoS_E # 打印日志结束C++ 编译器 将 函数模板 编译成了 汇编函数 call __Z3addIiET_S0_S0_ ;
如果 向 函数模板 中传入不同的函数 , 会生成 多个不同的 汇编函数 ;
C++ 编译器 编译 函数模板 时 , 不会生成能处理任意类型参数的 函数 ,
而是 通过 函数模板 , 根据 实际传入的参数类型 生成 具体的 参数类型不同 的函数 ;
如果 函数模板 和 普通函数 定义在了一起 ,
则 C++ 编译器 编译 汇编文件 时 , 就直接使用 普通函数 替代 为 函数模板 重新生成一个 函数实例 ;
C++ 编译器 通过 两次编译 实现上述效果 ;
如果 调用多次 , 那么会产生多个 新的函数模型 ;