C/C++汇编学习(四)——编写不同的C++程序并分析其汇编输出

发布时间:2024年01月05日

????????我们可以从一个简单的C++代码示例开始,然后生成其对应的汇编代码并进行解析。这个过程不仅展示了C++代码如何被转换成汇编语言,而且还帮助理解编译器是如何处理代码的。

案例一

C++ 代码示例

????????让我们使用一个简单的C++代码示例:一个计算两个数之和的函数。

#include <iostream>

int add(int a, int b) {
    return a + b;
}

int main() {
    int result = add(3, 4);
    std::cout << "The sum is: " << result << std::endl;
    return 0;
}

生成汇编代码

????????要生成这段代码的汇编版本,你可以使用 g++ 编译器(或任何其他支持的C++编译器)并使用 -S 选项。例如:

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

汇编代码解析

????????由于具体的汇编代码会根据编译器和目标架构的不同而有所差异,我将提供一个大致的解析,侧重于理解关键部分。

? ? ? ? 伪代码:

# 伪代码,具体汇编代码可能不同
.globl _Z3addii    # add函数的全局标记
_Z3addii:          # add函数标签
    movl %edi, -4(%rbp)  # 将第一个参数移动到栈上
    movl %esi, -8(%rbp)  # 将第二个参数移动到栈上
    movl -4(%rbp), %edx  # 将第一个参数加载到寄存器
    movl -8(%rbp), %eax  # 将第二个参数加载到寄存器
    addl %edx, %eax      # 将两个参数相加
    ret                  # 返回结果

.globl main       # main函数的全局标记
main:             # main函数标签
    # ...函数设置代码...
    movl $3, %edi  # 将3作为第一个参数
    movl $4, %esi  # 将4作为第二个参数
    call _Z3addii  # 调用add函数
    # ...打印结果代码...
    ret            # 返回

?

? ? ? ? 实际汇编,Ubuntu22.04 g++11输出的汇编。

	.file	"c.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 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	$4, %esi
	movl	$3, %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	-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:

?

????????注释解析?

.file   "c.cpp"  # 源文件名指定为 "c.cpp"
.text   # 开始代码段

# 初始化部分
.local  _ZStL8__ioinit  # 声明静态初始化对象 _ZStL8__ioinit (由C++库使用)
.comm   _ZStL8__ioinit,1,1  # 为 _ZStL8__ioinit 分配空间

# add 函数定义
.globl  _Z3addii  # 声明 add 函数为全局符号
.type   _Z3addii, @function  # 标记 _Z3addii 为函数类型
_Z3addii:  # add 函数的开始标记
.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)  # 将函数第一个参数(a)从 edi 寄存器存储到栈上
    movl    %esi, -8(%rbp)  # 将函数第二个参数(b)从 esi 寄存器存储到栈上
    movl    -4(%rbp), %edx  # 将 a 从栈上加载到 edx 寄存器
    movl    -8(%rbp), %eax  # 将 b 从栈上加载到 eax 寄存器
    addl    %edx, %eax  # 将 edx 和 eax 中的值相加,结果存储在 eax 中 (实现 return a + b;)
    popq    %rbp  # 从栈上恢复原基指针寄存器的值
    .cfi_def_cfa 7, 8
    ret  # 返回到调用函数
    .cfi_endproc
.LFE1731:
.size   _Z3addii, .-_Z3addii  # 指定 add 函数的大小

# 字符串常量定义
.section    .rodata  # 只读数据段
.LC0:
    .string "The sum is: "  # 存储字符串 "The sum is: " (对应 std::cout << "The sum is: ";)

.text   # 开始代码段
.globl  main  # 声明 main 函数为全局符号
.type   main, @function  # 标记 main 为函数类型
main:   # 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  # 为局部变量分配16字节的栈空间
    movl    $4, %esi  # 将整数 4 放入 esi 寄存器,作为 add 函数的第二个参数
    movl    $3, %edi  # 将整数 3 放入 edi 寄存器,作为 add 函数的第一个参数
    call    _Z3addii  # 调用 add 函数 (对应 int result = add(3, 4);)
    movl    %eax, -4(%rbp)  # 将 add 函数返回值存储到栈上 (对应 int result = add(3, 4);)
    leaq    .LC0(%rip), %rax  # 加载字符串 "The sum is: " 的地址到 rax (对应 std::cout << "The sum is: ";)
    movq    %rax, %rsi  # 将字符串地址移动到第二参数寄存器
    leaq    _ZSt4cout(%rip), %rax  # 加载 std::cout 对象的地址到 rax
    movq    %rax, %rdi  # 将 std::cout 地址移动到第一参数寄存器
    call    _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@PLT  # 调用输出操作符函数 (对应 std::cout << "The sum is: ";)
    movq    %rax, %rdx
    movl    -4(%rbp), %eax  # 将 add 函数的返回值加载到 eax (对应 std::cout << result;)
    movl    %eax, %esi  # 将返回值移动到第二参数寄存器
    movq    %rdx, %rdi  # 将 std::cout 对象地址移动到第一参数寄存器
    call    _ZNSolsEi@PLT  # 调用输出操作符函数 (对应 std::cout << result;)
    movq    _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_@GOTPCREL(%rip), %rdx  # 加载 std::endl 地址 (对应 std::cout << std::endl;)
    movq    %rdx, %rsi  # 将 std::endl 地址移动到第二参数寄存器
    movq    %rax, %rdi  # 将 std::cout 对象地址移动到第一参数寄存器
    call    _ZNSolsEPFRSoS_E@PLT  # 调用输出操作符函数 (对应 std::cout << std::endl;)
    movl    $0, %eax  # 将 0 放入 eax 作为函数返回值 (对应 return 0;)
    leave  # 清理栈帧并返回
    .cfi_def_cfa 7, 8
    ret  # 返回到调用函数
    .cfi_endproc
.LFE1732:
.size   main, .-main  # 指定 main 函数的大小

# 静态初始化部分
.type   _Z41__static_initialization_and_destruction_0ii, @function
...  # 静态初始化代码 (对应于C++全局和静态对象的构造和析构)

.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:

? ? ? ? C语言版的汇编

#include <stdio.h>

int add(int a, int b)
{
   return a+b;
}

int main()
{
   int result = add(4,5);
   printf("result: %d\n", result);
   return 0;
}
	.file	"c.c"
	.text
	.globl	add
	.type	add, @function
add:
.LFB0:
	.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
.LFE0:
	.size	add, .-add
	.section	.rodata
.LC0:
	.string	"result: %d\n"
	.text
	.globl	main
	.type	main, @function
main:
.LFB1:
	.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, %esi
	movl	$4, %edi
	call	add
	movl	%eax, -4(%rbp)
	movl	-4(%rbp), %eax
	movl	%eax, %esi
	leaq	.LC0(%rip), %rax
	movq	%rax, %rdi
	movl	$0, %eax
	call	printf@PLT
	movl	$0, %eax
	leave
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE1:
	.size	main, .-main
	.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:

?? ? ? ?G++与GCC区别

  1. 语言特性的差异

    • C语言不支持类和对象,因此在C的汇编代码中不会出现与类相关的构造函数、析构函数或成员函数调用。
    • C++支持更多的特性,如函数重载、模板、异常处理等,这些在C++生成的汇编代码中可能有所体现。
  2. 函数名称修饰(Name Mangling)

    • 在C++中,由于支持重载,函数名在编译时会经过修饰(Name Mangling)以包含更多信息(如参数类型等)。因此,C++生成的汇编代码中的函数名可能看起来更复杂。
    • C语言不支持重载,函数名称在汇编中保持原样。例如,这里的 add 函数在汇编中仍然是 add
  3. 标准库的使用

    • C++使用的是名为 iostream 的标准库进行输入输出,而C使用的是 stdio.h。因此,与输入输出相关的汇编代码会有所不同。
    • 在C++例子中,输出是通过 std::cout 实现的,而在C的示例中,是通过 printf 函数实现的。
  4. 汇编代码结构

    • 两种代码在汇编层面上的结构相似,因为它们都遵循类似的函数调用和栈管理规范。例如,都使用 pushq %rbpmovq %rsp, %rbp 来建立栈帧,使用 ret 返回等。
    • 在变量处理和函数调用方面,两者的汇编代码看起来非常相似。这是因为这些基础操作在C和C++中大致相同。
  5. 优化程度和编译器特定行为

    • 不同的编译器,甚至是同一编译器的不同版本或不同的编译选项,可能会产生不同的汇编代码。
    • 例如,某些优化可能会省略一些看似不必要的指令或重新排列指令的顺序。

? ? ? ? 总结

  1. 函数调用和栈管理:在 addmain 函数中,我们看到了如何建立和拆除栈帧。这包括保存基指针寄存器、设置新的栈基址、以及为局部变量分配栈空间。理解这些操作有助于掌握函数调用的内部机制,这在汇编级别编程中非常重要。

  2. 参数传递和寄存器使用:观察 add 函数如何接收其参数(通过寄存器 ediesi),以及这些参数如何被移动到栈上和寄存器中。这揭示了编译器如何利用寄存器和栈来传递参数。

  3. 基本指令集:通过这个例子,我们了解到了一些基础的汇编指令,如 movl(数据移动)、addl(加法)、call(函数调用)、ret(返回)等,这些都是汇编语言的基础。

  4. 系统调用和库函数main 函数展示了如何使用库函数(如 std::cout)来执行I/O操作。这些操作在汇编层面转化为一系列 call 指令和寄存器操作。

  5. 符号名字修饰(Name Mangling):C++中函数和变量的名字在编译后经过修饰(mangling),以支持诸如重载等特性。例如,add 函数变成了 _Z3addii

  6. 静态和全局对象初始化:理解C++中静态和全局对象是如何在程序开始前初始化的,这部分在汇编代码中通过静态初始化函数和段来处理。

  7. 汇编与高级语言的关系:理解高级语言(如C++)代码如何转换为底层汇编指令,这对于深入理解计算机程序的工作方式非常关键。

  8. 调试和逆向工程:这种从高级语言到汇编的映射对于调试低级错误、进行性能优化以及逆向工程非常有用。

案例二

#include <iostream>

class Point {
private:
    int x, y;

public:
    Point() : x(0), y(0) {}  // 默认构造函数

    void setCoordinates(int newX, int newY) {
        x = newX;
        y = newY;
    }

    void printCoordinates() const {
        std::cout << "(" << x << ", " << y << ")" << std::endl;
    }
};

int main() {
    Point point;
    point.setCoordinates(5, 3);
    point.printCoordinates();
    return 0;
}

汇编代码:

	.file	"c.cpp"
	.text
	.local	_ZStL8__ioinit
	.comm	_ZStL8__ioinit,1,1
	.section	.text._ZN5PointC2Ev,"axG",@progbits,_ZN5PointC5Ev,comdat
	.align 2
	.weak	_ZN5PointC2Ev
	.type	_ZN5PointC2Ev, @function
_ZN5PointC2Ev:
.LFB1732:
	.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	-8(%rbp), %rax
	movl	$0, (%rax)
	movq	-8(%rbp), %rax
	movl	$0, 4(%rax)
	nop
	popq	%rbp
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE1732:
	.size	_ZN5PointC2Ev, .-_ZN5PointC2Ev
	.weak	_ZN5PointC1Ev
	.set	_ZN5PointC1Ev,_ZN5PointC2Ev
	.section	.text._ZN5Point14setCoordinatesEii,"axG",@progbits,_ZN5Point14setCoordinatesEii,comdat
	.align 2
	.weak	_ZN5Point14setCoordinatesEii
	.type	_ZN5Point14setCoordinatesEii, @function
_ZN5Point14setCoordinatesEii:
.LFB1734:
	.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)
	movl	%esi, -12(%rbp)
	movl	%edx, -16(%rbp)
	movq	-8(%rbp), %rax
	movl	-12(%rbp), %edx
	movl	%edx, (%rax)
	movq	-8(%rbp), %rax
	movl	-16(%rbp), %edx
	movl	%edx, 4(%rax)
	nop
	popq	%rbp
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE1734:
	.size	_ZN5Point14setCoordinatesEii, .-_ZN5Point14setCoordinatesEii
	.section	.rodata
.LC0:
	.string	"("
.LC1:
	.string	", "
.LC2:
	.string	")"
	.section	.text._ZNK5Point16printCoordinatesEv,"axG",@progbits,_ZNK5Point16printCoordinatesEv,comdat
	.align 2
	.weak	_ZNK5Point16printCoordinatesEv
	.type	_ZNK5Point16printCoordinatesEv, @function
_ZNK5Point16printCoordinatesEv:
.LFB1735:
	.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
	movq	%rax, %rdx
	movq	-8(%rbp), %rax
	movl	(%rax), %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
	movq	-8(%rbp), %rax
	movl	4(%rax), %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	_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_@GOTPCREL(%rip), %rdx
	movq	%rdx, %rsi
	movq	%rax, %rdi
	call	_ZNSolsEPFRSoS_E@PLT
	nop
	leave
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE1735:
	.size	_ZNK5Point16printCoordinatesEv, .-_ZNK5Point16printCoordinatesEv
	.text
	.globl	main
	.type	main, @function
main:
.LFB1736:
	.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	%fs:40, %rax
	movq	%rax, -8(%rbp)
	xorl	%eax, %eax
	leaq	-16(%rbp), %rax
	movq	%rax, %rdi
	call	_ZN5PointC1Ev
	leaq	-16(%rbp), %rax
	movl	$3, %edx
	movl	$5, %esi
	movq	%rax, %rdi
	call	_ZN5Point14setCoordinatesEii
	leaq	-16(%rbp), %rax
	movq	%rax, %rdi
	call	_ZNK5Point16printCoordinatesEv
	movl	$0, %eax
	movq	-8(%rbp), %rdx
	subq	%fs:40, %rdx
	je	.L6
	call	__stack_chk_fail@PLT
.L6:
	leave
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE1736:
	.size	main, .-main
	.type	_Z41__static_initialization_and_destruction_0ii, @function
_Z41__static_initialization_and_destruction_0ii:
.LFB2239:
	.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
.LFE2239:
	.size	_Z41__static_initialization_and_destruction_0ii, .-_Z41__static_initialization_and_destruction_0ii
	.type	_GLOBAL__sub_I_main, @function
_GLOBAL__sub_I_main:
.LFB2240:
	.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
.LFE2240:
	.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:

汇编伪代码:

# 类 Point 的默认构造函数
Point::Point():
    push rbp                   # 保存旧的基指针
    mov rbp, rsp               # 更新基指针
    mov [rbp-4], rdi           # 将 this 指针存储到局部变量
    mov rax, [rbp-4]           # 加载 this 指针
    mov dword ptr [rax], 0     # 将 x 初始化为 0
    mov dword ptr [rax+4], 0   # 将 y 初始化为 0
    pop rbp                    # 恢复基指针
    ret                        # 返回

# 类 Point 的 setCoordinates 函数
Point::setCoordinates(int newX, int newY):
    push rbp                   # 保存旧的基指针
    mov rbp, rsp               # 更新基指针
    mov [rbp-4], rdi           # 将 this 指针存储到局部变量
    mov [rbp-8], esi           # 将 newX 存储到局部变量
    mov [rbp-12], edx          # 将 newY 存储到局部变量
    mov rax, [rbp-4]           # 加载 this 指针
    mov edx, [rbp-8]           # 加载 newX
    mov [rax], edx             # 更新 x 成员
    mov edx, [rbp-12]          # 加载 newY
    mov [rax+4], edx           # 更新 y 成员
    pop rbp                    # 恢复基指针
    ret                        # 返回

# 类 Point 的 printCoordinates 函数
Point::printCoordinates() const:
    push rbp                   # 保存旧的基指针
    mov rbp, rsp               # 更新基指针
    mov [rbp-4], rdi           # 将 this 指针存储到局部变量
    mov rax, [rbp-4]           # 加载 this 指针
    mov ecx, [rax]             # 加载 x 成员
    mov edx, [rax+4]           # 加载 y 成员
    # 调用 std::cout 相关函数来输出 "(",x,", ",y 和 ")"
    pop rbp                    # 恢复基指针
    ret                        # 返回

# main 函数
main:
    push rbp                   # 保存旧的基指针
    mov rbp, rsp               # 更新基指针
    sub rsp, 16                # 为 Point 对象分配栈空间
    lea rdi, [rbp-16]          # 将 Point 对象的地址放入 rdi
    call Point::Point          # 调用 Point 的构造函数
    lea rdi, [rbp-16]          # 将 Point 对象的地址放入 rdi
    mov esi, 5                 # 将 5 作为 newX 参数放入 esi
    mov edx, 3                 # 将 3 作为 newY 参数放入 edx
    call Point::setCoordinates # 调用 setCoordinates 函数
    lea rdi, [rbp-16]          # 将 Point 对象的地址放入 rdi
    call Point::printCoordinates # 调用 printCoordinates 函数
    mov eax, 0                 # 将 0 放入 eax 作为返回值
    leave                      # 恢复栈和基指针
    ret                        # 返回

总结?

这个C++转汇编的案例为理解C++代码如何转换为底层机器码提供了重要的启示:

  1. 类构造和析构:理解类的构造函数和析构函数如何在汇编层面操作内存,尤其是如何初始化类成员变量,是深入理解对象生命周期的关键。

  2. 成员函数调用:通过分析成员函数的汇编代码,我们可以看到 this 指针是如何被处理和传递的。这有助于理解对象方法在内存中是如何与其数据成员关联的。

  3. 对象的内存布局:观察对象在内存中是如何布局的,特别是成员变量在对象内存结构中的位置。这对于理解面向对象编程的内存管理非常重要。

  4. 堆栈操作:类方法中的堆栈操作(如保存基指针、调整栈指针等)展示了函数调用的常见模式。理解这些模式对于深入学习函数的工作原理至关重要。

  5. 参数传递和寄存器使用:成员函数的参数传递、局部变量的处理以及寄存器的使用揭示了编译器如何优化函数调用和数据存储。

  6. 高级特性的底层实现:C++的高级特性(如类、对象、成员函数)在汇编层面的实现帮助我们理解这些特性的工作原理和潜在开销。

  7. 标准库函数的调用:如何在汇编中处理C++标准库函数的调用(例如,std::cout 的使用)。

  8. 调试和优化:对汇编代码的理解可以在调试时帮助更好地识别和修复底层的错误,并为性能优化提供线索。

?

未完待续

文章来源:https://blog.csdn.net/weixin_44120785/article/details/135397492
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。