【C++】C++函数模板的实现机制剖析

函数模板的实现机制剖析

要剖析函数模板的实现机制,我们要先了解程序的编译过程,这里以gcc为例

这里对下面代码进行剖析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include "stdafx.h"//这是VS的固有头文件
#include <iostream>
using namespace std;

template <typename T>
void Fun(T a,T b)
{
a = a + b;
cout << "我是函数模板" << endl;
}
int main()
{
Fun(1, 2);
Fun(0.1, 0.2);
system("pause");
return 0;
}

打开控制台,使用命令生成汇编文件(当然环境变量里需要有g++编译器的路径才能这样使用,具体操作放在文末)

1
g++ -S 1.cpp -o 1.s

我们来查看汇编文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
	.file	"C++.cpp"
.lcomm __ZStL8__ioinit,1,1
.def ___main; .scl 2; .type 32; .endef
.section .rdata,"dr"
LC2:
.ascii "pause\0"
.text
.globl _main
.def _main; .scl 2; .type 32; .endef
_main:
LFB1062:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
andl $-16, %esp
subl $16, %esp
call ___main//--------------进入main函数
movl $2, 4(%esp)
movl $1, (%esp)
call __Z3FunIiEvT_S0_//-----第一次调用函数模板
fldl LC0
fstpl 8(%esp)
fldl LC1
fstpl (%esp)
call __Z3FunIdEvT_S0_//-----第二次调用函数模板
movl $LC2, (%esp)
call _system
movl $0, %eax
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
LFE1062:
.section .rdata,"dr"
LC4:
.ascii "\316\322\312\307\272\257\312\375\304\243\260\345\0"
.section .text$_Z3FunIiEvT_S0_,"x"
.linkonce discard
.globl __Z3FunIiEvT_S0_
.def __Z3FunIiEvT_S0_; .scl 2; .type 32; .endef
__Z3FunIiEvT_S0_://------------第一次调用时函数模板的具体实现
LFB1063:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
subl $24, %esp
movl 12(%ebp), %eax
addl %eax, 8(%ebp)
movl $LC4, 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
nop
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
LFE1063:
.section .text$_Z3FunIdEvT_S0_,"x"
.linkonce discard
.globl __Z3FunIdEvT_S0_//-第二次调用时函数模板的具体实现
.def __Z3FunIdEvT_S0_; .scl 2; .type 32; .endef
__Z3FunIdEvT_S0_:
LFB1064:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
subl $40, %esp
movl 8(%ebp), %eax
movl %eax, -16(%ebp)
movl 12(%ebp), %eax
movl %eax, -12(%ebp)
movl 16(%ebp), %eax
movl %eax, -24(%ebp)
movl 20(%ebp), %eax
movl %eax, -20(%ebp)
fldl -16(%ebp)
faddl -24(%ebp)
fstpl -16(%ebp)
movl $LC4, 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
nop
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
LFE1064:
.text
.def ___tcf_0; .scl 3; .type 32; .endef
___tcf_0:
LFB1074:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
subl $8, %esp
movl $__ZStL8__ioinit, %ecx
call __ZNSt8ios_base4InitD1Ev
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
LFE1074:
.def __Z41__static_initialization_and_destruction_0ii; .scl 3; .type 32; .endef
__Z41__static_initialization_and_destruction_0ii:
LFB1073:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
subl $24, %esp
cmpl $1, 8(%ebp)
jne L8
cmpl $65535, 12(%ebp)
jne L8
movl $__ZStL8__ioinit, %ecx
call __ZNSt8ios_base4InitC1Ev
movl $___tcf_0, (%esp)
call _atexit
L8:
nop
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
LFE1073:
.def __GLOBAL__sub_I_main; .scl 3; .type 32; .endef
__GLOBAL__sub_I_main:
LFB1075:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
subl $24, %esp
movl $65535, 4(%esp)
movl $1, (%esp)
call __Z41__static_initialization_and_destruction_0ii
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
LFE1075:
.section .ctors,"w"
.align 4
.long __GLOBAL__sub_I_main
.section .rdata,"dr"
.align 8
LC0:
.long -1717986918
.long 1070176665
.align 8
LC1:
.long -1717986918
.long 1069128089
.ident "GCC: (i686-posix-dwarf-rev0, Built by MinGW-W64 project) 5.3.0"
.def _system; .scl 2; .type 32; .endef
.def __ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc; .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

可以看到,在汇编码中编译器将模板void Fun(T a,T b)分别针对类型int和类型float进行了两次具体实现,这不是和没有使用模板,直接写两个函数一样么?没错就是一样的,只是C++将这个工作交从程序员手里移交给了编译器来做。

事实上C++对函数模板进行了两次编译,第一次编译仅仅生成一个函数头,第二次编译则是在函数调用时根据模板的类型参数列表具体的实现这个模板对应的类型的函数实例,注意这里是根据类型参数列表来实现,而不是根据调用次数,如:

1
2
Fun(1, 2);
Fun(0.1, 0.2);

编译器实现两个模板实例intfloat

1
2
3
Fun(1, 2);
Fun(3 ,4);
Fun(0.1, 0.2);

编译器还是实现两个模板实例intfloat

由此可以看出

编译器并不是把函数模板处理成能够处理任意类的函数

编译器将函数模板根据具体类型产生不同的函数

编译器会对函数模板进行两次编译,在申明的地方对模板代码本身进行编译,在调用的地方对参数替换后的代码进行编译。

g++命令的使用

首先电脑里要有g++这个软件,我这里使用的是Qt里集成的g++软件

然后右键我的电脑–>属性–>高级环境设置–>环境变量–>系统变量/Path–>编辑

再然后新建–>将g++.exe所在的路径拷贝到新建的环境变量中

测试一下,win+r–>cmd–>任意目录键入g++

可以看到系统没有提示无此命令,说明配置成功


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!