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

函数模板的实现机制剖析

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

【C++】C++函数模板的实现机制剖析_第1张图片
这里对下面代码进行剖析

#include "stdafx.h"//这是VS的固有头文件
#include 
using namespace std;

template 
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++编译器的路径才能这样使用,具体操作放在文末)

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

我们来查看汇编文件

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

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

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

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

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

由此可以看出

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

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

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

g++命令的使用

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

【C++】C++函数模板的实现机制剖析_第2张图片
然后右键我的电脑–>属性–>高级环境设置–>环境变量–>系统变量/Path–>编辑

【C++】C++函数模板的实现机制剖析_第3张图片
再然后新建–>将g++.exe所在的路径拷贝到新建的环境变量中

【C++】C++函数模板的实现机制剖析_第4张图片

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

【C++】C++函数模板的实现机制剖析_第5张图片

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

你可能感兴趣的:(知识记录,C++,C++)