C++编译器函数模版机制剖析 - 函数模版的本质

时间:2023-03-08 19:15:05

思考:为什么函数模板能够和函数重载放在一块。C++编译器是怎样提供函数模板机制的?

demo 1

#include <cstdio>
#include <iostream>
using namespace std; // 1.cpp // g++ -S 1.cpp -o 1.s
template <typename T>
void myswap(T &a, T &b)
{
T c = 0;
c = a;
a = b;
b = c;
cout << "hello ....我是模板函数 欢迎 calll 我" << endl;
} int main()
{
{ int x = 10;
int y = 20; myswap<int>(x, y); //1 函数模板 显示类型 调用 printf("x:%d y:%d \n", x, y);
} {
char a = 'a';
char b = 'b'; myswap<char>(a, b); //1 函数模板 显示类型 调用
printf("a:%c b:%c \n", a, b);
}
return 0;
}

把demo 1编译成汇编文件,查看:

	.file	"1.cpp"
.lcomm __ZStL8__ioinit,1,1
.def ___main; .scl 2; .type 32; .endef
.section .rdata,"dr"
LC0:
.ascii "x:%d y:%d \12\0"
LC1:
.ascii "a:%c b:%c \12\0"
.def ___gxx_personality_sj0; .scl 2; .type 32; .endef
.def __Unwind_SjLj_Register; .scl 2; .type 32; .endef
.def __Unwind_SjLj_Unregister; .scl 2; .type 32; .endef
.text
.globl _main
.def _main; .scl 2; .type 32; .endef
_main:
pushl %ebp
movl %esp, %ebp
pushl %edi
pushl %esi
pushl %ebx
andl $-16, %esp
subl $96, %esp
movl $___gxx_personality_sj0, 52(%esp)
movl $LLSDA959, 56(%esp)
leal 60(%esp), %eax
movl %ebp, (%eax)
movl $L5, %edx
movl %edx, 4(%eax)
movl %esp, 8(%eax)
leal 28(%esp), %eax
movl %eax, (%esp)
call __Unwind_SjLj_Register
call ___main
movl $10, 92(%esp)
movl $20, 88(%esp)
leal 88(%esp), %eax
movl %eax, 4(%esp)
leal 92(%esp), %eax
movl %eax, (%esp)
movl $1, 32(%esp)
<span style="color:#ff0000;"> call __Z6myswapIiEvRT_S1_ // 41 call 117</span>
movl 88(%esp), %edx
movl 92(%esp), %eax
movl %edx, 8(%esp)
movl %eax, 4(%esp)
movl $LC0, (%esp)
call _printf
movb $97, 87(%esp)
movb $98, 86(%esp)
leal 86(%esp), %eax
movl %eax, 4(%esp)
leal 87(%esp), %eax
movl %eax, (%esp)
movl $2, 32(%esp)
<span style="color:#ff0000;"> call __Z6myswapIcEvRT_S1_ // 55 call 145</span>
movb 86(%esp), %al
movsbl %al, %edx
movb 87(%esp), %al
movsbl %al, %eax
movl %edx, 8(%esp)
movl %eax, 4(%esp)
movl $LC1, (%esp)
call _printf
movl $0, %eax
movl %eax, 24(%esp)
jmp L8
L5:
movl 36(%esp), %edx
movl 32(%esp), %eax
testl %eax, %eax
je L6
cmpl $1, %eax
je L7
.word 0x0b0f
L6:
movl %edx, %eax
movl %eax, (%esp)
movl $-1, 32(%esp)
call __Unwind_SjLj_Resume
L7:
movl %edx, %eax
movl %eax, (%esp)
movl $-1, 32(%esp)
call __Unwind_SjLj_Resume
L8:
leal 28(%esp), %eax
movl %eax, (%esp)
call __Unwind_SjLj_Unregister
movl 24(%esp), %eax
leal -12(%ebp), %esp
popl %ebx
popl %esi
popl %edi
popl %ebp
ret
.section .gcc_except_table,"w"
LLSDA959:
.byte 0xff
.byte 0xff
.byte 0x1
.uleb128 LLSDACSE959-LLSDACSB959
LLSDACSB959:
.uleb128 0
.uleb128 0
.uleb128 0x1
.uleb128 0
LLSDACSE959:
.text
.section .rdata,"dr"
.align 4
LC2:
.ascii "hello ....\316\322\312\307\304\243\260\345\272\257\312\375 \273\266\323\255 calll \316\322\0"
.section .text$_Z6myswapIiEvRT_S1_,"x"
.linkonce discard
.globl __Z6myswapIiEvRT_S1_
.def __Z6myswapIiEvRT_S1_; .scl 2; .type 32; .endef
<span style="color:#ff0000;">__Z6myswapIiEvRT_S1_: // 117</span>
pushl %ebp
movl %esp, %ebp
subl $40, %esp
movl $0, -12(%ebp)
movl 8(%ebp), %eax
movl (%eax), %eax
movl %eax, -12(%ebp)
movl 12(%ebp), %eax
movl (%eax), %edx
movl 8(%ebp), %eax
movl %edx, (%eax)
movl 12(%ebp), %eax
movl -12(%ebp), %edx
movl %edx, (%eax)
movl $LC2, 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
leave
ret
.section .text$_Z6myswapIcEvRT_S1_,"x"
.linkonce discard
.globl __Z6myswapIcEvRT_S1_
.def __Z6myswapIcEvRT_S1_; .scl 2; .type 32; .endef
<span style="color:#ff0000;">__Z6myswapIcEvRT_S1_: // 145</span>
pushl %ebp
movl %esp, %ebp
subl $40, %esp
movb $0, -9(%ebp)
movl 8(%ebp), %eax
movb (%eax), %al
movb %al, -9(%ebp)
movl 12(%ebp), %eax
movb (%eax), %dl
movl 8(%ebp), %eax
movb %dl, (%eax)
movl 12(%ebp), %eax
movb -9(%ebp), %dl
movb %dl, (%eax)
movl $LC2, 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
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 L12
cmpl $65535, 12(%ebp)
jne L12
movl $__ZStL8__ioinit, %ecx
call __ZNSt8ios_base4InitC1Ev
movl $___tcf_0, (%esp)
call _atexit
L12:
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
.def __Unwind_SjLj_Resume; .scl 2; .type 32; .endef
.def _printf; .scl 2; .type 32; .endef
.def __ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_; .scl 2; .type 32; .endef
.def __ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc; .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

观察发现一个现象,myswap函数模版有一个声明,两个定义,这样的情况和我在“为什么会有函数模版中”博文中提到的两个myswap函数非常相似,实际这里体现了C++实现函数模版的本。本来须要程序猿依据须要去写非常多个逻辑同样,參数不同的函数。可是C++编译器帮我们做了这件事,依据调用会自己主动生成这些函数。这也是为什么函数模版能够和普通函数放在一起。

总结:函数模版机制结论:

编译器并非把函数模版处理成可以处理随意类的函数;

编译器从函数模版通过详细类型产生不同的函数;

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