C++从汇编的视角审视对象的创建问题

C++从汇编的视角审视对象的创建问题

前言

很久以前阅读了 CSAPP 这本书,可惜看过的东西基本都忘记了,只知道一些工具可以帮助我分析。今天突然对 “返回对象的函数” 很感兴趣,于是分析了一下汇编。

返回对象的函数如下,现在有一种叫做 NRV 优化的技术可以避免低效率,如果把它关掉,它将会执行以下过程:创建一个对象 apple,然后返回一个 apple,发生一次拷贝构造函数,所以存在执行效率问题;如果你还写了 Apple a = GetApple();,那么还将会发生一次赋值拷贝构造。

Apple GetApple() { Apple apple{}; return apple; }

代码:

class Apple { public: Apple() { } ~Apple() { } Apple(const Apple& apple) { this->a = apple.a; this->b = apple.b; } Apple& operator=(const Apple& apple) { this->a = apple.a; this->b = apple.b; return *this; } void Print() { cout << a << " " << b << endl; } int a = 1; int b = 2; }; Apple GetApple() { Apple apple{}; return apple; } 构造函数的执行过程分析

构造函数的执行过程:即使是无参构造函数,调用之前仍然要传参。传入的是一个地址,需要在函数运行结束的时候,这个地址上有了一个对象。

main 函数如下:

int main() { Apple a; int x = a.a; x = 10; return 0; }

生成的汇编代码:

# main 函数构造对象的地方: pushq%rbp # rbp 压栈 movq%rsp, %rbp # rsp 替换 rbp pushq%rbx # rbx 压栈,保存现场 subq$24, %rsp # rsp 自减 24,栈向下增长,相当于扩展 24 字节 leaq-24(%rbp), %rax # 将地址赋值给 rax,rax 是 Apple 对象开始的地方 movq%rax, %rdi # rdi 传参 call_ZN5AppleC1Ev movl-28(%rbp), %eax # 返回之后,会执行 int x = a.a; movl%eax, -20(%rbp) # 取第一个字节赋值给 x movl$10, -20(%rbp) # 直接使用 10 赋值给 x;写着行的目的只是为了确定 x 的位置 # 默认构造器: pushq%rbp # rbp 压栈 movq%rsp, %rbp # rsp 替换 rbp movq%rdi, -8(%rbp) # rdi 是前面 rax 的值 movq-8(%rbp), %rax # 设置 rax 为 rbp 减 8 movl$1, (%rax) # 间接寻址,设置 4 字节为 1 movq-8(%rbp), %rax # rax 其实还是一样的 movl$2, 4(%rax) # 变址寻址,其实就是间接寻址加一个偏移量,设置 4 字节为 2 nop popq%rbp ret # 于是最终 rdi 开始,向下的 8 字节是 Apple 对象 返回对象函数的分析

从汇编的视角来看,调用构造器和调用 “返回对象” 的函数是一样的。它的执行过程是:即使是两个函数都是无参的,它仍然会进行一次传参。传入的是一个地址,需要在函数调用结束后,这个地址上有了一个对象。从汇编的角度来看,对象就是一堆数据的排列,比如说最普通的对象就是数据成员按照声明顺序直接排列。

举个实际点的例子。Apple 这个类,有两个成员,a 和 b。调用了构造器或者 “返回对象” 的函数,先传入一个地址,之后函数里面会在这个地址上存放两个数,分别是 a 和 b 的值,然后返回。此时,这个地址上就有了 a 和 b,虽然机器看不到对象,但是对我们来说,它就是一个对象。

如果关闭了 NRV 优化,那么首先会传入一个地址,然后构造一个对象,这个对象将会被拷贝构造到传入的地址上,返回。之后如果调用了一次赋值,那么还要将这个地址上的对象复制构造给栈上的变量。整个过程,实际上存在两个临时对象,发生了一次构造、一次复制构造、一次赋值构造。

main: .LFB3535: pushq%rbp movq%rsp, %rbp pushq%rbx subq$40, %rsp leaq-36(%rbp), %rax movq%rax, %rdi call_ZN5AppleC1Ev movl-36(%rbp), %eax movl%eax, -20(%rbp) movl$10, -20(%rbp) leaq-28(%rbp), %rax # 这里之前的汇编是一样的,调用 GetApple movq%rax, %rdi # rdi,传参 call_Z8GetApplev leaq-28(%rbp), %rdx # rax 已经存放对象,-28 的位置有对象 leaq-36(%rbp), %rax movq%rdx, %rsi movq%rax, %rdi call_ZN5AppleaSERKS_ leaq-28(%rbp), %rax movq%rax, %rdi call_ZN5AppleD1Ev movl$0, %ebx leaq-36(%rbp), %rax movq%rax, %rdi call_ZN5AppleD1Ev movl%ebx, %eax addq$40, %rsp popq%rbx popq%rbp # GetApple 函数的汇编代码 pushq%rbp movq%rsp, %rbp subq$32, %rsp movq%rdi, -24(%rbp) # 分配空间,然后将传过来的 rdi 放进去 leaq-8(%rbp), %rax movq%rax, %rdi # 前面分析过了,调用回来时 rax 开始的 8 字节就是对象 call_ZN5AppleC1Ev leaq-8(%rbp), %rdx # 取 rax 地址到 rdx movq-24(%rbp), %rax # 取 rdi 地址到 rax movq%rdx, %rsi # rsi 已经有对象了 movq%rax, %rdi # rsi 和 rdi 都用来传参 call_ZN5AppleC1ERKS_ # 调用拷贝构造函数 leaq-8(%rbp), %rax # rax 上有对象了 movq%rax, %rdi # rdi 传参,准备调用析构函数 call_ZN5AppleD1Ev nop movq-24(%rbp), %rax # 前面调用拷贝构造前的地址,这个地址有对象了 leave ret # 拷贝构造函数 pushq%rbp movq%rsp, %rbp movq%rdi, -8(%rbp) # rdi 无对象 movq%rsi, -16(%rbp) # rsi 有对象 movq-16(%rbp), %rax # 把对象放到 rax movl(%rax), %edx # 把第一属性(4 字节)移动到 edx movq-8(%rbp), %rax movl%edx, (%rax) # 把第一属性移动到 rdi movq-16(%rbp), %rax movl4(%rax), %edx # 把第二属性移动到 edx movq-8(%rbp), %rax movl%edx, 4(%rax) # 把第二属性移动到 rdi 上 movq-8(%rbp), %rax # rdi 上有了对象,rax 也设置一个 popq%rbp ret

到此这篇关于C++:从汇编的视角看对象的创建的文章就介绍到这了,更多相关C++汇编对象内容请搜索易知道(ezd.cc)以前的文章或继续浏览下面的相关文章希望大家以后多多支持易知道(ezd.cc)!

推荐阅读

    excel怎么用乘法函数

    excel怎么用乘法函数,乘法,函数,哪个,excel乘法函数怎么用?1、首先用鼠标选中要计算的单元格。2、然后选中单元格后点击左上方工具栏的fx公

    excel中乘法函数是什么?

    excel中乘法函数是什么?,乘法,函数,什么,打开表格,在C1单元格中输入“=A1*B1”乘法公式。以此类推到多个单元。1、A1*B1=C1的Excel乘法公式

    电脑店u修复工具|u盘修复电脑工具

    电脑店u修复工具|u盘修复电脑工具,,u盘修复电脑工具你好,1、电脑管家是没有修复U盘的功能的。所以不好修复U盘的。2、如果要修复U盘的话,可

    标准差excel用什么函数?

    标准差excel用什么函数?,函数,标准,什么,在数据单元格的下方输入l标准差公式函数公式“=STDEVPA(C2:C6)”。按下回车,求出标准公差值。详细

    你启动win7pe工具箱安装使用图文教程

    你启动win7pe工具箱安装使用图文教程,,点评:首先,你开始windows7pe工具箱下载到本地计算机。建议下载到计算机桌面,然后很容易地找到它,然后按