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

 更新时间:2022年01月21日 09:20:06   作者:楷哥  
这篇文章主要介绍了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
	movl	4(%rax), %edx     # 把第二属性移动到 edx
	movq	-8(%rbp), %rax
	movl	%edx, 4(%rax)     # 把第二属性移动到 rdi 上
	movq	-8(%rbp), %rax    # rdi 上有了对象,rax 也设置一个
	popq	%rbp
	ret

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

相关文章

  • c++ *运算符重载

    c++ *运算符重载

    运算符重载重载运算符是C++ 的一个重要特性,使用运算符重载, 的一个重要特性,使用运算符重载, 重载运算符是程序员可以把C++ 运算符的定义扩展到运算分量是对象
    2014-09-09
  • C++产生随机数的实现代码

    C++产生随机数的实现代码

    本篇文章是对C++中产生随机数的实现代码进行了详细的分析介绍,需要的朋友参考下
    2013-05-05
  • 详解C++语言中std::array的神奇用法

    详解C++语言中std::array的神奇用法

    本文的代码都在C++17环境下编译运行。当前主流的g++版本已经能支持C++17标准,但是很多版本(如gcc 7.3)的C++17特性不是默认打开的,需要手工添加编译选项-std=c++17,具体内容详情跟随小编一起学习吧
    2021-05-05
  • C++ deque与vector对比的优缺点

    C++ deque与vector对比的优缺点

    这篇文章主要介绍了C++中deque与vector相比的优势与劣势,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习吧
    2023-01-01
  • c++隐式类型转换存在的问题解析

    c++隐式类型转换存在的问题解析

    隐式转换,是指不需要用户干预,编译器私下进行的类型转换行为,很多时候用户都不知道具体进行了哪些转换,这篇文章主要介绍了c++隐式类型转换存在的陷阱,需要的朋友可以参考下
    2022-03-03
  • C语言智能指针之weak_ptr浅析

    C语言智能指针之weak_ptr浅析

    这篇文章主要介绍了 C++11智能指针之weak_ptr详解,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-10-10
  • C++实现简单迷宫游戏

    C++实现简单迷宫游戏

    这篇文章主要为大家详细介绍了C++实现简单迷宫游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-01-01
  • C++ opencv ffmpeg图片序列化实现代码解析

    C++ opencv ffmpeg图片序列化实现代码解析

    这篇文章主要介绍了C++ opencv ffmpeg图片序列化实现代码解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-08-08
  • 用C语言实现推箱子游戏实例

    用C语言实现推箱子游戏实例

    大家好,本篇文章主要讲的是用C语言实现推箱子游戏实例,感兴趣的同学赶快来看一看吧,对你有帮助的话记得收藏一下
    2022-01-01
  • C语言中 & 和 &&的区别详解

    C语言中 & 和 &&的区别详解

    这篇文章主要介绍了C语言中 & 和 &&的区别详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-01-01

最新评论