记录一些C++相关的笔记

121 阅读9分钟

源码1 函数之间值传递

#include <iostream>

// 定义一个函数,计算两个整数的和
int add(int a, int b) {
    return a + b;
}

int main() {
    int num1 = 5;
    int num2 = 10;

    // 调用 add 函数,传入 num1 和 num2 的值
    int sum = add(num1, num2);

    // 输出结果
    std::cout << "The sum of " << num1 << " and " << num2 << " is: " << sum << std::endl;

    return 0;
}

源码1对应的汇编

	.file	"simple_function.cpp"
	.text
	.local	_ZStL8__ioinit
	.comm	_ZStL8__ioinit,1,1
	.globl	_Z3addii
	.type	_Z3addii, @function
_Z3addii:
.LFB1731:
	.cfi_startproc
	endbr64
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	movl	%edi, -4(%rbp)
	movl	%esi, -8(%rbp)
	movl	-4(%rbp), %edx
	movl	-8(%rbp), %eax
	addl	%edx, %eax
	popq	%rbp
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE1731:
	.size	_Z3addii, .-_Z3addii
	.section	.rodata
.LC0:
	.string	"The sum of "
.LC1:
	.string	" and "
.LC2:
	.string	" is: "
	.text
	.globl	main
	.type	main, @function
main:
.LFB1732:
	.cfi_startproc
	endbr64
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	subq	$16, %rsp
	movl	$5, -12(%rbp)
	movl	$10, -8(%rbp)
	movl	-8(%rbp), %edx
	movl	-12(%rbp), %eax
	movl	%edx, %esi
	movl	%eax, %edi
	call	_Z3addii
	movl	%eax, -4(%rbp)
	leaq	.LC0(%rip), %rax
	movq	%rax, %rsi
	leaq	_ZSt4cout(%rip), %rax
	movq	%rax, %rdi
	call	_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@PLT
	movq	%rax, %rdx
	movl	-12(%rbp), %eax
	movl	%eax, %esi
	movq	%rdx, %rdi
	call	_ZNSolsEi@PLT
	movq	%rax, %rdx
	leaq	.LC1(%rip), %rax
	movq	%rax, %rsi
	movq	%rdx, %rdi
	call	_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@PLT
	movq	%rax, %rdx
	movl	-8(%rbp), %eax
	movl	%eax, %esi
	movq	%rdx, %rdi
	call	_ZNSolsEi@PLT
	movq	%rax, %rdx
	leaq	.LC2(%rip), %rax
	movq	%rax, %rsi
	movq	%rdx, %rdi
	call	_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@PLT
	movq	%rax, %rdx
	movl	-4(%rbp), %eax
	movl	%eax, %esi
	movq	%rdx, %rdi
	call	_ZNSolsEi@PLT
	movq	_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_@GOTPCREL(%rip), %rdx
	movq	%rdx, %rsi
	movq	%rax, %rdi
	call	_ZNSolsEPFRSoS_E@PLT
	movl	$0, %eax
	leave
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE1732:
	.size	main, .-main
	.type	_Z41__static_initialization_and_destruction_0ii, @function
_Z41__static_initialization_and_destruction_0ii:
.LFB2235:
	.cfi_startproc
	endbr64
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	subq	$16, %rsp
	movl	%edi, -4(%rbp)
	movl	%esi, -8(%rbp)
	cmpl	$1, -4(%rbp)
	jne	.L7
	cmpl	$65535, -8(%rbp)
	jne	.L7
	leaq	_ZStL8__ioinit(%rip), %rax
	movq	%rax, %rdi
	call	_ZNSt8ios_base4InitC1Ev@PLT
	leaq	__dso_handle(%rip), %rax
	movq	%rax, %rdx
	leaq	_ZStL8__ioinit(%rip), %rax
	movq	%rax, %rsi
	movq	_ZNSt8ios_base4InitD1Ev@GOTPCREL(%rip), %rax
	movq	%rax, %rdi
	call	__cxa_atexit@PLT
.L7:
	nop
	leave
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE2235:
	.size	_Z41__static_initialization_and_destruction_0ii, .-_Z41__static_initialization_and_destruction_0ii
	.type	_GLOBAL__sub_I__Z3addii, @function
_GLOBAL__sub_I__Z3addii:
.LFB2236:
	.cfi_startproc
	endbr64
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	movl	$65535, %esi
	movl	$1, %edi
	call	_Z41__static_initialization_and_destruction_0ii
	popq	%rbp
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE2236:
	.size	_GLOBAL__sub_I__Z3addii, .-_GLOBAL__sub_I__Z3addii
	.section	.init_array,"aw"
	.align 8
	.quad	_GLOBAL__sub_I__Z3addii
	.hidden	__dso_handle
	.ident	"GCC: (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0"
	.section	.note.GNU-stack,"",@progbits
	.section	.note.gnu.property,"a"
	.align 8
	.long	1f - 0f
	.long	4f - 1f
	.long	5
0:
	.string	"GNU"
1:
	.align 8
	.long	0xc0000002
	.long	3f - 2f
2:
	.long	0x3
3:
	.align 8
4:

总结

main函数核心调用add部分:

movl	$5, -12(%rbp) ;将立即数5移动到栈帧中相对于 %rbp 偏移 -12 字节的位置,即初始化局部变量 num1 为 5。
movl	$10, -8(%rbp) ;将立即数10移动到栈帧中相对于 %rbp 偏移 -8 字节的位置,即初始化局部变量 num2 为 10。
movl	-8(%rbp), %edx ;将 num2 的值从栈帧中加载到寄存器 %edx。
movl	-12(%rbp), %eax ;将 num1 的值从栈帧中加载到寄存器 %eax。
movl	%edx, %esi ;将 %edx (即 num2) 的值移动到寄存器 %esi,作为 add 函数的第二个参数。
movl	%eax, %edi ;将 %eax (即 num1) 的值移动到寄存器 %edi,作为 add 函数的第一个参数。
call	_Z3addii ;调用add函数

add被调用的核心部分:

movl	%edi, -4(%rbp) ;将第一个参数(存储在寄存器%edi中)移动到栈帧中相对于%rbp偏移-4字节的位置。
movl	%esi, -8(%rbp) ;将第二个参数(存储在寄存器%esi中)移动到栈帧中相对于%rbp偏移-8字节的位置。
movl	-4(%rbp), %edx ;将第一个参数从栈帧中加载到寄存器%edx
movl	-8(%rbp), %eax ;将第二个参数从栈帧中加载到寄存器%eax
addl	%edx, %eax ;将%edx和%eax中的值相加,结果存储在%eax中

最终是将edi和esi中的值拷贝到eax和edx中进行操作的。

源码2 函数之间引用传递

#include <iostream>

// 定义一个函数,计算两个整数的和,并通过引用修改传入的参数
void add(int& a, int& b, int& result) {
    result = a + b;
}

int main() {
    int num1 = 5;
    int num2 = 10;
    int sum; // 用于存储结果的变量

    std::cout << "Before add: num1 = " << num1 << ", num2 = " << num2 << std::endl;

    // 调用 add 函数,传入 num1、num2 和 sum 的引用
    add(num1, num2, sum);

    std::cout << "After add: num1 = " << num1 << ", num2 = " << num2 << ", sum = " << sum << std::endl;

    return 0;
}

源码2对应的汇编

		.file	"add_by_reference.cpp"
	.text
	.local	_ZStL8__ioinit
	.comm	_ZStL8__ioinit,1,1
	.globl	_Z3addRiS_S_
	.type	_Z3addRiS_S_, @function
_Z3addRiS_S_:
.LFB1731:
	.cfi_startproc
	endbr64
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	movq	%rdi, -8(%rbp)
	movq	%rsi, -16(%rbp)
	movq	%rdx, -24(%rbp)
	movq	-8(%rbp), %rax
	movl	(%rax), %edx
	movq	-16(%rbp), %rax
	movl	(%rax), %eax
	addl	%eax, %edx
	movq	-24(%rbp), %rax
	movl	%edx, (%rax)
	nop
	popq	%rbp
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE1731:
	.size	_Z3addRiS_S_, .-_Z3addRiS_S_
	.section	.rodata
.LC0:
	.string	"After add: num1 = "
.LC1:
	.string	", num2 = "
.LC2:
	.string	", sum = "
	.text
	.globl	main
	.type	main, @function
main:
.LFB1732:
	.cfi_startproc
	endbr64
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	subq	$32, %rsp
	movq	%fs:40, %rax
	movq	%rax, -8(%rbp)
	xorl	%eax, %eax
	movl	$5, -20(%rbp)
	movl	$10, -16(%rbp)
	leaq	-12(%rbp), %rdx
	leaq	-16(%rbp), %rcx
	leaq	-20(%rbp), %rax
	movq	%rcx, %rsi
	movq	%rax, %rdi
	call	_Z3addRiS_S_
	leaq	.LC0(%rip), %rax
	movq	%rax, %rsi
	leaq	_ZSt4cout(%rip), %rax
	movq	%rax, %rdi
	call	_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@PLT
	movq	%rax, %rdx
	movl	-20(%rbp), %eax
	movl	%eax, %esi
	movq	%rdx, %rdi
	call	_ZNSolsEi@PLT
	movq	%rax, %rdx
	leaq	.LC1(%rip), %rax
	movq	%rax, %rsi
	movq	%rdx, %rdi
	call	_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@PLT
	movq	%rax, %rdx
	movl	-16(%rbp), %eax
	movl	%eax, %esi
	movq	%rdx, %rdi
	call	_ZNSolsEi@PLT
	movq	%rax, %rdx
	leaq	.LC2(%rip), %rax
	movq	%rax, %rsi
	movq	%rdx, %rdi
	call	_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@PLT
	movq	%rax, %rdx
	movl	-12(%rbp), %eax
	movl	%eax, %esi
	movq	%rdx, %rdi
	call	_ZNSolsEi@PLT
	movq	_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_@GOTPCREL(%rip), %rdx
	movq	%rdx, %rsi
	movq	%rax, %rdi
	call	_ZNSolsEPFRSoS_E@PLT
	movl	$0, %eax
	movq	-8(%rbp), %rdx
	subq	%fs:40, %rdx
	je	.L4
	call	__stack_chk_fail@PLT
.L4:
	leave
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE1732:
	.size	main, .-main
	.type	_Z41__static_initialization_and_destruction_0ii, @function
_Z41__static_initialization_and_destruction_0ii:
.LFB2235:
	.cfi_startproc
	endbr64
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	subq	$16, %rsp
	movl	%edi, -4(%rbp)
	movl	%esi, -8(%rbp)
	cmpl	$1, -4(%rbp)
	jne	.L7
	cmpl	$65535, -8(%rbp)
	jne	.L7
	leaq	_ZStL8__ioinit(%rip), %rax
	movq	%rax, %rdi
	call	_ZNSt8ios_base4InitC1Ev@PLT
	leaq	__dso_handle(%rip), %rax
	movq	%rax, %rdx
	leaq	_ZStL8__ioinit(%rip), %rax
	movq	%rax, %rsi
	movq	_ZNSt8ios_base4InitD1Ev@GOTPCREL(%rip), %rax
	movq	%rax, %rdi
	call	__cxa_atexit@PLT
.L7:
	nop
	leave
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE2235:
	.size	_Z41__static_initialization_and_destruction_0ii, .-_Z41__static_initialization_and_destruction_0ii
	.type	_GLOBAL__sub_I__Z3addRiS_S_, @function
_GLOBAL__sub_I__Z3addRiS_S_:
.LFB2236:
	.cfi_startproc
	endbr64
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	movl	$65535, %esi
	movl	$1, %edi
	call	_Z41__static_initialization_and_destruction_0ii
	popq	%rbp
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE2236:
	.size	_GLOBAL__sub_I__Z3addRiS_S_, .-_GLOBAL__sub_I__Z3addRiS_S_
	.section	.init_array,"aw"
	.align 8
	.quad	_GLOBAL__sub_I__Z3addRiS_S_
	.hidden	__dso_handle
	.ident	"GCC: (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0"
	.section	.note.GNU-stack,"",@progbits
	.section	.note.gnu.property,"a"
	.align 8
	.long	1f - 0f
	.long	4f - 1f
	.long	5
0:
	.string	"GNU"
1:
	.align 8
	.long	0xc0000002
	.long	3f - 2f
2:
	.long	0x3
3:
	.align 8
4:

总结

main函数调用部分的核心代码:

leaq 是 "Load Effective Address quadword" 的缩写。leaq 指令的主要作用是将一个内存地址计算出来,并将其加载到一个 64 位寄存器中。

movl	$5, -20(%rbp) ;将 num1 初始化为 5,存储在 rbp - 20 的位置。
movl	$10, -16(%rbp) ;将 num2 初始化为 10,存储在 rbp - 16 的位置。
leaq	-12(%rbp), %rdx ;将 sum 的地址放入 rdx 寄存器。
leaq	-16(%rbp), %rcx ;将 num2 的地址放入 rcx 寄存器。
leaq	-20(%rbp), %rax ;将 num1 的地址放入 rax 寄存器。
movq	%rcx, %rsi ;将 num2 的地址从 rcx 复制到 rsi 寄存器,作为第二个参数传递给 add
movq	%rax, %rdi ;将 num1 的地址从 rax 复制到 rdi 寄存器,作为第一个参数传递给 add
call	_Z3addRiS_S_ ;调用add函数

add函数的执行部分:

movq	%rdi, -8(%rbp) ;将第一个参数(num1 的地址)存储到 rbp - 8 的位置。
movq	%rsi, -16(%rbp) ;将第二个参数(num2 的地址)存储到 rbp - 16 的位置。
movq	%rdx, -24(%rbp) ;将第三个参数(sum 的地址)存储到 rbp - 24 的位置。
movq	-8(%rbp), %rax ;将 num1 的地址加载到 rax 寄存器。
movl	(%rax), %edx ;将 num1 的值(通过解引用地址)加载到 edx 寄存器。
movq	-16(%rbp), %rax ;将 num2 的地址加载到 rax 寄存器。
movl	(%rax), %eax ;将 num2 的值加载到 eax 寄存器。
addl	%eax, %edx ;将 eax 和 edx 的值相加,结果存入 edx。
movq	-24(%rbp) ;%rax ;将 sum 的地址加载到 rax 寄存器。
movl	%edx, (%rax) ;将计算结果(sum 的值)存储到 sum 的地址所指向的内存位置。

兜兜转转了一圈,引用传递实际上传输的是地址,操作的是该地址对应的值。所以被调用的函数会改变调用方的值。

源码3 模板函数

#include <iostream>

// 定义一个模板函数,计算两个任意类型数据的和
template <typename T>
T add(T a, T b) {
    return a + b;
}

int main() {
    int num1 = 5;
    int num2 = 10;

    // 调用 add 函数,传入 int 类型数据
    int sum_int = add(num1, num2);

    double num3 = 3.14;
    double num4 = 2.71;

    // 调用 add 函数,传入 double 类型数据
    double sum_double = add(num3, num4);
    std::cout << "The sum of " << num3 << " and " << num4 << " is: " << sum_double << std::endl;

    return 0;
}

源码3对应汇编

	.file	"template_function.cpp"
	.text
	.local	_ZStL8__ioinit
	.comm	_ZStL8__ioinit,1,1
	.section	.rodata
.LC2:
	.string	"The sum of "
.LC3:
	.string	" and "
.LC4:
	.string	" is: "
	.text
	.globl	main
	.type	main, @function
main:
.LFB1732:
	.cfi_startproc
	endbr64
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	subq	$48, %rsp
	movl	$5, -36(%rbp)
	movl	$10, -32(%rbp)
	movl	-32(%rbp), %edx
	movl	-36(%rbp), %eax
	movl	%edx, %esi
	movl	%eax, %edi
	call	_Z3addIiET_S0_S0_
	movl	%eax, -28(%rbp)
	movsd	.LC0(%rip), %xmm0
	movsd	%xmm0, -24(%rbp)
	movsd	.LC1(%rip), %xmm0
	movsd	%xmm0, -16(%rbp)
	movsd	-16(%rbp), %xmm0
	movq	-24(%rbp), %rax
	movapd	%xmm0, %xmm1
	movq	%rax, %xmm0
	call	_Z3addIdET_S0_S0_
	movq	%xmm0, %rax
	movq	%rax, -8(%rbp)
	leaq	.LC2(%rip), %rax
	movq	%rax, %rsi
	leaq	_ZSt4cout(%rip), %rax
	movq	%rax, %rdi
	call	_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@PLT
	movq	%rax, %rdx
	movq	-24(%rbp), %rax
	movq	%rax, %xmm0
	movq	%rdx, %rdi
	call	_ZNSolsEd@PLT
	movq	%rax, %rdx
	leaq	.LC3(%rip), %rax
	movq	%rax, %rsi
	movq	%rdx, %rdi
	call	_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@PLT
	movq	%rax, %rdx
	movq	-16(%rbp), %rax
	movq	%rax, %xmm0
	movq	%rdx, %rdi
	call	_ZNSolsEd@PLT
	movq	%rax, %rdx
	leaq	.LC4(%rip), %rax
	movq	%rax, %rsi
	movq	%rdx, %rdi
	call	_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@PLT
	movq	%rax, %rdx
	movq	-8(%rbp), %rax
	movq	%rax, %xmm0
	movq	%rdx, %rdi
	call	_ZNSolsEd@PLT
	movq	_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_@GOTPCREL(%rip), %rdx
	movq	%rdx, %rsi
	movq	%rax, %rdi
	call	_ZNSolsEPFRSoS_E@PLT
	movl	$0, %eax
	leave
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE1732:
	.size	main, .-main
	.section	.text._Z3addIiET_S0_S0_,"axG",@progbits,_Z3addIiET_S0_S0_,comdat
	.weak	_Z3addIiET_S0_S0_
	.type	_Z3addIiET_S0_S0_, @function
_Z3addIiET_S0_S0_:
.LFB1993:
	.cfi_startproc
	endbr64
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	movl	%edi, -4(%rbp)
	movl	%esi, -8(%rbp)
	movl	-4(%rbp), %edx
	movl	-8(%rbp), %eax
	addl	%edx, %eax
	popq	%rbp
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE1993:
	.size	_Z3addIiET_S0_S0_, .-_Z3addIiET_S0_S0_
	.section	.text._Z3addIdET_S0_S0_,"axG",@progbits,_Z3addIdET_S0_S0_,comdat
	.weak	_Z3addIdET_S0_S0_
	.type	_Z3addIdET_S0_S0_, @function
_Z3addIdET_S0_S0_:
.LFB1994:
	.cfi_startproc
	endbr64
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	movsd	%xmm0, -8(%rbp)
	movsd	%xmm1, -16(%rbp)
	movsd	-8(%rbp), %xmm0
	addsd	-16(%rbp), %xmm0
	movq	%xmm0, %rax
	movq	%rax, %xmm0
	popq	%rbp
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE1994:
	.size	_Z3addIdET_S0_S0_, .-_Z3addIdET_S0_S0_
	.text
	.type	_Z41__static_initialization_and_destruction_0ii, @function
_Z41__static_initialization_and_destruction_0ii:
.LFB2238:
	.cfi_startproc
	endbr64
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	subq	$16, %rsp
	movl	%edi, -4(%rbp)
	movl	%esi, -8(%rbp)
	cmpl	$1, -4(%rbp)
	jne	.L9
	cmpl	$65535, -8(%rbp)
	jne	.L9
	leaq	_ZStL8__ioinit(%rip), %rax
	movq	%rax, %rdi
	call	_ZNSt8ios_base4InitC1Ev@PLT
	leaq	__dso_handle(%rip), %rax
	movq	%rax, %rdx
	leaq	_ZStL8__ioinit(%rip), %rax
	movq	%rax, %rsi
	movq	_ZNSt8ios_base4InitD1Ev@GOTPCREL(%rip), %rax
	movq	%rax, %rdi
	call	__cxa_atexit@PLT
.L9:
	nop
	leave
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE2238:
	.size	_Z41__static_initialization_and_destruction_0ii, .-_Z41__static_initialization_and_destruction_0ii
	.type	_GLOBAL__sub_I_main, @function
_GLOBAL__sub_I_main:
.LFB2239:
	.cfi_startproc
	endbr64
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	movl	$65535, %esi
	movl	$1, %edi
	call	_Z41__static_initialization_and_destruction_0ii
	popq	%rbp
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE2239:
	.size	_GLOBAL__sub_I_main, .-_GLOBAL__sub_I_main
	.section	.init_array,"aw"
	.align 8
	.quad	_GLOBAL__sub_I_main
	.section	.rodata
	.align 8
.LC0:
	.long	1374389535
	.long	1074339512
	.align 8
.LC1:
	.long	2061584302
	.long	1074114068
	.hidden	__dso_handle
	.ident	"GCC: (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0"
	.section	.note.GNU-stack,"",@progbits
	.section	.note.gnu.property,"a"
	.align 8
	.long	1f - 0f
	.long	4f - 1f
	.long	5
0:
	.string	"GNU"
1:
	.align 8
	.long	0xc0000002
	.long	3f - 2f
2:
	.long	0x3
3:
	.align 8
4:

总结

模板函数不为特定数据类型编写,而是定义了一个通用算法框架。在编译时,根据传入的实际数据类型,编译器会生成对应的函数实例。

模板函数的多态性在编译时确定,不同于虚函数的运行时多态。

模板函数实例化 (_Z3addIiET_S0_S0_, _Z3addIdET_S0_S0_):

  • 汇编代码中出现了两个函数:_Z3addIiET_S0_S0_ (对应 add<int>) 和 _Z3addIdET_S0_S0_ (对应 add<double>)。它们是编译器根据传入的 intdouble 类型分别实例化的 add 模板函数。
  • 从实例化函数的汇编可以看出,它们的核心逻辑非常相似:获取参数、执行加法操作、返回结果。只不过一个是整数加法 (addl),一个是浮点数加法 (addsd)。这体现了模板函数的通用算法框架。

main 函数调用 (call _Z3addIiET_S0_S0_, call _Z3addIdET_S0_S0_):

  • main 函数中,对 add 函数的两次调用实际上是调用了对应的模板函数实例。编译器在编译时已经根据参数类型确定了要调用的具体函数实例。

源码4 虚函数 运行时确定调用对象

// can show dynamic calling process
#include <iostream>

class Base {
public:
    virtual void func() {
        std::cout << "Base::func()\n";
    }
};

class Derived : public Base {
public:
    void func() override {
        std::cout << "Derived::func()\n";
    }
};

int main() {
    Base *b = new Derived();
    b->func(); // 虚函数调用
    delete b;
    return 0;
}

源码4对应的汇编

	.file	"virtual.cpp"
	.text
	.local	_ZStL8__ioinit
	.comm	_ZStL8__ioinit,1,1
	.section	.rodata
.LC0:
	.string	"Base::func()\n"
	.section	.text._ZN4Base4funcEv,"axG",@progbits,_ZN4Base4funcEv,comdat
	.align 2
	.weak	_ZN4Base4funcEv
	.type	_ZN4Base4funcEv, @function
_ZN4Base4funcEv:
.LFB1731:
	.cfi_startproc
	endbr64
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	subq	$16, %rsp
	movq	%rdi, -8(%rbp)
	leaq	.LC0(%rip), %rax
	movq	%rax, %rsi
	leaq	_ZSt4cout(%rip), %rax
	movq	%rax, %rdi
	call	_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@PLT
	nop
	leave
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE1731:
	.size	_ZN4Base4funcEv, .-_ZN4Base4funcEv
	.section	.rodata
.LC1:
	.string	"Derived::func()\n"
	.section	.text._ZN7Derived4funcEv,"axG",@progbits,_ZN7Derived4funcEv,comdat
	.align 2
	.weak	_ZN7Derived4funcEv
	.type	_ZN7Derived4funcEv, @function
_ZN7Derived4funcEv:
.LFB1732:
	.cfi_startproc
	endbr64
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	subq	$16, %rsp
	movq	%rdi, -8(%rbp)
	leaq	.LC1(%rip), %rax
	movq	%rax, %rsi
	leaq	_ZSt4cout(%rip), %rax
	movq	%rax, %rdi
	call	_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@PLT
	nop
	leave
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE1732:
	.size	_ZN7Derived4funcEv, .-_ZN7Derived4funcEv
	.section	.text._ZN4BaseC2Ev,"axG",@progbits,_ZN4BaseC5Ev,comdat
	.align 2
	.weak	_ZN4BaseC2Ev
	.type	_ZN4BaseC2Ev, @function
_ZN4BaseC2Ev:
.LFB1736:
	.cfi_startproc
	endbr64
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	movq	%rdi, -8(%rbp)
	leaq	16+_ZTV4Base(%rip), %rdx
	movq	-8(%rbp), %rax
	movq	%rdx, (%rax)
	nop
	popq	%rbp
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE1736:
	.size	_ZN4BaseC2Ev, .-_ZN4BaseC2Ev
	.weak	_ZN4BaseC1Ev
	.set	_ZN4BaseC1Ev,_ZN4BaseC2Ev
	.section	.text._ZN7DerivedC2Ev,"axG",@progbits,_ZN7DerivedC5Ev,comdat
	.align 2
	.weak	_ZN7DerivedC2Ev
	.type	_ZN7DerivedC2Ev, @function
_ZN7DerivedC2Ev:
.LFB1738:
	.cfi_startproc
	endbr64
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	subq	$16, %rsp
	movq	%rdi, -8(%rbp)
	movq	-8(%rbp), %rax
	movq	%rax, %rdi
	call	_ZN4BaseC2Ev
	leaq	16+_ZTV7Derived(%rip), %rdx
	movq	-8(%rbp), %rax
	movq	%rdx, (%rax)
	nop
	leave
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE1738:
	.size	_ZN7DerivedC2Ev, .-_ZN7DerivedC2Ev
	.weak	_ZN7DerivedC1Ev
	.set	_ZN7DerivedC1Ev,_ZN7DerivedC2Ev
	.text
	.globl	main
	.type	main, @function
main:
.LFB1733:
	.cfi_startproc
	endbr64
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	pushq	%rbx
	subq	$24, %rsp
	.cfi_offset 3, -24
	movl	$8, %edi
	call	_Znwm@PLT
	movq	%rax, %rbx
	movq	$0, (%rbx)
	movq	%rbx, %rdi
	call	_ZN7DerivedC1Ev
	movq	%rbx, -24(%rbp)
	movq	-24(%rbp), %rax
	movq	(%rax), %rax
	movq	(%rax), %rdx
	movq	-24(%rbp), %rax
	movq	%rax, %rdi
	call	*%rdx
	movq	-24(%rbp), %rax
	testq	%rax, %rax
	je	.L6
	movl	$8, %esi
	movq	%rax, %rdi
	call	_ZdlPvm@PLT
.L6:
	movl	$0, %eax
	movq	-8(%rbp), %rbx
	leave
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE1733:
	.size	main, .-main
	.weak	_ZTV7Derived
	.section	.data.rel.ro.local._ZTV7Derived,"awG",@progbits,_ZTV7Derived,comdat
	.align 8
	.type	_ZTV7Derived, @object
	.size	_ZTV7Derived, 24
_ZTV7Derived:
	.quad	0
	.quad	_ZTI7Derived
	.quad	_ZN7Derived4funcEv
	.weak	_ZTV4Base
	.section	.data.rel.ro.local._ZTV4Base,"awG",@progbits,_ZTV4Base,comdat
	.align 8
	.type	_ZTV4Base, @object
	.size	_ZTV4Base, 24
_ZTV4Base:
	.quad	0
	.quad	_ZTI4Base
	.quad	_ZN4Base4funcEv
	.weak	_ZTI7Derived
	.section	.data.rel.ro._ZTI7Derived,"awG",@progbits,_ZTI7Derived,comdat
	.align 8
	.type	_ZTI7Derived, @object
	.size	_ZTI7Derived, 24
_ZTI7Derived:
	.quad	_ZTVN10__cxxabiv120__si_class_type_infoE+16
	.quad	_ZTS7Derived
	.quad	_ZTI4Base
	.weak	_ZTS7Derived
	.section	.rodata._ZTS7Derived,"aG",@progbits,_ZTS7Derived,comdat
	.align 8
	.type	_ZTS7Derived, @object
	.size	_ZTS7Derived, 9
_ZTS7Derived:
	.string	"7Derived"
	.weak	_ZTI4Base
	.section	.data.rel.ro._ZTI4Base,"awG",@progbits,_ZTI4Base,comdat
	.align 8
	.type	_ZTI4Base, @object
	.size	_ZTI4Base, 16
_ZTI4Base:
	.quad	_ZTVN10__cxxabiv117__class_type_infoE+16
	.quad	_ZTS4Base
	.weak	_ZTS4Base
	.section	.rodata._ZTS4Base,"aG",@progbits,_ZTS4Base,comdat
	.type	_ZTS4Base, @object
	.size	_ZTS4Base, 6
_ZTS4Base:
	.string	"4Base"
	.text
	.type	_Z41__static_initialization_and_destruction_0ii, @function
_Z41__static_initialization_and_destruction_0ii:
.LFB2237:
	.cfi_startproc
	endbr64
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	subq	$16, %rsp
	movl	%edi, -4(%rbp)
	movl	%esi, -8(%rbp)
	cmpl	$1, -4(%rbp)
	jne	.L10
	cmpl	$65535, -8(%rbp)
	jne	.L10
	leaq	_ZStL8__ioinit(%rip), %rax
	movq	%rax, %rdi
	call	_ZNSt8ios_base4InitC1Ev@PLT
	leaq	__dso_handle(%rip), %rax
	movq	%rax, %rdx
	leaq	_ZStL8__ioinit(%rip), %rax
	movq	%rax, %rsi
	movq	_ZNSt8ios_base4InitD1Ev@GOTPCREL(%rip), %rax
	movq	%rax, %rdi
	call	__cxa_atexit@PLT
.L10:
	nop
	leave
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE2237:
	.size	_Z41__static_initialization_and_destruction_0ii, .-_Z41__static_initialization_and_destruction_0ii
	.type	_GLOBAL__sub_I_main, @function
_GLOBAL__sub_I_main:
.LFB2238:
	.cfi_startproc
	endbr64
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	movl	$65535, %esi
	movl	$1, %edi
	call	_Z41__static_initialization_and_destruction_0ii
	popq	%rbp
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE2238:
	.size	_GLOBAL__sub_I_main, .-_GLOBAL__sub_I_main
	.section	.init_array,"aw"
	.align 8
	.quad	_GLOBAL__sub_I_main
	.hidden	__dso_handle
	.ident	"GCC: (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0"
	.section	.note.GNU-stack,"",@progbits
	.section	.note.gnu.property,"a"
	.align 8
	.long	1f - 0f
	.long	4f - 1f
	.long	5
0:
	.string	"GNU"
1:
	.align 8
	.long	0xc0000002
	.long	3f - 2f
2:
	.long	0x3
3:
	.align 8
4:

总结

main:
.LFB1733:         # 函数开始标签
    .cfi_startproc   
    endbr64          # 用于增强代码安全性,防止控制流劫持
    pushq   %rbp     # 保存基址寄存器 %rbp 的值到栈上
    .cfi_def_cfa_offset 16  
    .cfi_offset 6, -16 
    movq    %rsp, %rbp    # 将栈顶指针 %rsp 赋值给 %rbp,作为新的基址
    .cfi_def_cfa_register 6 
    pushq   %rbx     # 保存通用寄存器 %rbx 的值到栈上
    subq    $24, %rsp  # 调整栈指针,为局部变量分配 24 字节空间
    .cfi_offset 3, -24

    # --- 1. 分配内存 ---
    movl    $8, %edi     # 将要分配的内存大小 (8 字节) 放入寄存器 %edi
    call    _Znwm@PLT  # %edi会被当做该函数的参数。调用 _Znwm 函数 (operator new) 分配内存,返回地址存入 %rax

    # --- 2. 存储 vtable 指针 ---
    movq    %rax, %rbx    # 将分配的内存地址保存到寄存器 %rbx 中
    movq    $0, (%rbx)    # 将虚表指针置为 0 (这将在构造函数中被正确设置) 

    # --- 3. 构造对象 ---
    movq    %rbx, %rdi    # 将对象地址 (现在是 %rbx) 放入寄存器 %rdi,作为 this 指针传给构造函数,new 出来的这块内存给赋给 this。js也是这样。
    call    _ZN7DerivedC1Ev  # 调用 Derived 类的构造函数,new出的那块内存的内存布局,其中包括虚表指针。

    # --- 4. 存储对象指针到栈 ---
    movq    %rbx, -24(%rbp) # 将对象地址存储到栈上,便于后续使用

    # --- 5. 虚函数调用 ---
    movq    -24(%rbp), %rax   # 从栈中加载对象地址到 %rax
    movq    (%rax), %rax      # 获取对象的虚表指针
    movq    (%rax), %rdx      # 获取虚表中第一个虚函数 (func) 的地址
    movq    -24(%rbp), %rax   # 再次加载对象地址,作为 this 指针
    movq    %rax, %rdi        # 将 this 指针放入 %rdi,%rdi稍后会在被调函数中当做参数使用
    call    *%rdx          # 调用虚函数,至此我们可以看到rdx是通过计算得到的。计算逻辑为:new 出一块内存,this指向的就是这块内存-->根据代码逻辑调用对应的构造函数初始化this指向的这块内存,其中初始化过程中会将虚表指针初始化为这块内存的前8个字节-->获取this对象的前8个字节,这8个字节记录着虚表指针-->根据虚表指针解析到虚表指针对应的位置。

    # --- 6. 释放内存 ---
    movq    -24(%rbp), %rax   # 加载对象地址
    testq   %rax, %rax       # 检查对象地址是否为 NULL
    je      .L6           # 如果是 NULL,跳转到 .L6

    movl    $8, %esi         # 将要释放的内存大小 (8 字节) 放入寄存器 %esi
    movq    %rax, %rdi        # 将对象地址放入 %rdi
    call    _ZdlPvm@PLT    # 调用 _ZdlPvm 函数 (operator delete) 释放内存

.L6:                        # .L6 标签
    movl    $0, %eax        # 将返回值设置为 0
    movq    -8(%rbp), %rbx # 恢复 %rbx 的值
    leave                  # 恢复栈帧
    .cfi_def_cfa 7, 8     
    ret                     # 函数返回

所谓虚函数的调用是在运行时确定的,站在汇编的角度,意思是具体调用哪个函数是根据this指向中的vptr确定的,但是this又是根据构造函数确定的,所以只有在执行了构造函数以后,this对应的那块内存才能被初始化完成。