服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - C/C++ - C++从汇编的视角审视对象的创建问题

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

2022-09-01 11:36楷哥 C/C++

这篇文章主要介绍了C++从汇编的视角看对象的创建,从汇编的视角来看,调用构造器和调用 “返回对象” 的函数是一样的,从汇编的角度来看,对象就是一堆数据的排列,比如说最普通的对象就是数据成员按照声明顺序直接排列,

前言

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

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

?
1
2
3
4
Apple GetApple() {
    Apple apple{};
    return apple;
}

代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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 函数如下:

?
1
2
3
4
5
6
int main() {
  Apple a;
  int x = a.a;
  x = 10;
  return 0;
}

生成的汇编代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 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 优化,那么首先会传入一个地址,然后构造一个对象,这个对象将会被拷贝构造到传入的地址上,返回。之后如果调用了一次赋值,那么还要将这个地址上的对象复制构造给栈上的变量。整个过程,实际上存在两个临时对象,发生了一次构造、一次复制构造、一次赋值构造。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
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++汇编对象内容请搜索服务器之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持服务器之家!

原文链接:https://www.cnblogs.com/zzk0/p/15827536.html

延伸 · 阅读

精彩推荐
  • C/C++C++可变参数的函数与模板实例分析

    C++可变参数的函数与模板实例分析

    这篇文章主要介绍了C++可变参数的函数与模板,非常重要的概念,需要的朋友可以参考下...

    C++教程网9572021-01-28
  • C/C++C++顺序表实现图书管理系统

    C++顺序表实现图书管理系统

    这篇文章主要为大家详细介绍了C++顺序表实现图书管理系统,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    Doraemon0212124022022-01-21
  • C/C++C++实现哈夫曼树简单创建与遍历的方法

    C++实现哈夫曼树简单创建与遍历的方法

    这篇文章主要介绍了C++实现哈夫曼树简单创建与遍历的方法,对于C++算法的学习来说不失为一个很好的借鉴实例,需要的朋友可以参考下...

    C++教程网10932021-01-24
  • C/C++浅谈C语言=与==的区别详解

    浅谈C语言=与==的区别详解

    这篇文章主要介绍了浅谈C语言=与==的区别详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随...

    zx9462021-10-21
  • C/C++使用boost读取XML文件详细介绍

    使用boost读取XML文件详细介绍

    这篇文章主要介绍了使用boost读取XML文件详细介绍的相关资料,需要的朋友可以参考下...

    松阳11082021-04-20
  • C/C++C++ OpenCV技术实战之身份证离线识别

    C++ OpenCV技术实战之身份证离线识别

    OpenCV身份证离线识别技术的主要技术就是通过OpenCV找到身份证号码区域,然后通过OCR进行数字识别该区域的截图即可得到身份证号码。感兴趣的可以了解一...

    Tim8922022-08-01
  • C/C++c++异常处理机制示例及详细讲解

    c++异常处理机制示例及详细讲解

    本篇文章主要是对c++异常处理机制示例进行了介绍,需要的朋友可以过来参考下,希望对大家有所帮助...

    C++教程网7102021-01-15
  • C/C++C/C++ Qt 数据库与TreeView组件绑定详解

    C/C++ Qt 数据库与TreeView组件绑定详解

    本篇文章主要介绍了QT数据库与View组件的绑定,通过数据库与组件关联可实现动态展示数据库中的表记录。感兴趣的小伙伴可以了解一下...

    LyShark11422022-03-10