博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
从汇编角度看C++类的方法访问类成员的原理
阅读量:4206 次
发布时间:2019-05-26

本文共 5571 字,大约阅读时间需要 18 分钟。

C++编译后最终也是生成了机器码,不需要解释器或虚拟机来运行。相比C语言,C++的类大大的方便了代码结构的组织,使得构建大程序简便容易了很多。实例化一个类后,类的成员方法就可以访问这个类的成员了,那么从汇编角度看,到底是如何实现的呢?其实这个原理也十分简单,类实例后的对象从内存上和其所有成员变量组成的结构体是一样的,每个类的方法第一个入参就是把这个结构体的地址穿进去,类的方法通过这样的机制实现了访问类的成员。Python的类中self满天飞机制上也有点类似。下面用一个例子来具体看下:

class Test{public:    int a;    int b;    int c;    int d;    int e;    Test(int a, int b, int c, int d, int e) {        this->a = a;        this->b = b;        this->c = c;        this->d = d;        this->e = e;    }    int fun() {        int a1 = a;        int b1 = b;        int c1 = c;        int d1 = d;        int e1 = e;        return 100;    }};int main(){    Test test(1, 2, 3, 4, 5);    test.fun();    return 0;}

上面的代码非常简单,主要看一下反汇编后的汇编代码,用下面命令编译 g++ -g -m64 main.c, 编译生成 a.out ELF文件, 然后反汇编 objdump -dS a.out, 即可看到源代码和反汇编的汇编代码一一对应的呈现, 也可以 gdb a.out, 用 disass /m fun_name 看与源码对应的函数反汇编。

首先看下类Test的大小, Test类型大小就是其成员变量组成的结构体的大小。

(gdb) pt Testtype = class Test {  public:    int a;    int b;    int c;    int d;    int e;    Test(int, int, int, int, int);    int fun(void);}(gdb) p sizeof(Test)$1 = 20

然后看下Test类构造函数的反汇编,函数调用约定可以看:,按照标准调用约定,第一个入参 %rdi 寄存器并不是代码形参,而就是这个对象本身的地址。

Test(int a, int b, int c, int d, int e) {  //400530:   55                      push   %rbp  //400531:   48 89 e5                mov    %rsp,%rbp  //400534:   48 89 7d f8             mov    %rdi,-0x8(%rbp)  // 把调用约定参数1,test对应实例的地址  //400538:   89 75 f4                mov    %esi,-0xc(%rbp)  // 调用约定里参数2  //40053b:   89 55 f0                mov    %edx,-0x10(%rbp) // 调用约定里参数3  //40053e:   89 4d ec                mov    %ecx,-0x14(%rbp) // 调用约定里参数4  //400541:   44 89 45 e8             mov    %r8d,-0x18(%rbp) // 调用约定里参数5  //400545:   44 89 4d e4             mov    %r9d,-0x1c(%rbp) // 调用约定里参数6        this->a = a;  //400549:   48 8b 45 f8             mov    -0x8(%rbp),%rax  //40054d:   8b 55 f4                mov    -0xc(%rbp),%edx  //400550:   89 10                   mov    %edx,(%rax)        this->b = b;  //400552:   48 8b 45 f8             mov    -0x8(%rbp),%rax  //400556:   8b 55 f0                mov    -0x10(%rbp),%edx  //400559:   89 50 04                mov    %edx,0x4(%rax)        this->c = c;  //40055c:   48 8b 45 f8             mov    -0x8(%rbp),%rax  //400560:   8b 55 ec                mov    -0x14(%rbp),%edx  //400563:   89 50 08                mov    %edx,0x8(%rax)        this->d = d;  //400566:   48 8b 45 f8             mov    -0x8(%rbp),%rax  //40056a:   8b 55 e8                mov    -0x18(%rbp),%edx  //40056d:   89 50 0c                mov    %edx,0xc(%rax)        this->e = e;  //400570:   48 8b 45 f8             mov    -0x8(%rbp),%rax  //400574:   8b 55 e4                mov    -0x1c(%rbp),%edx  //400577:   89 50 10                mov    %edx,0x10(%rax)    }  //40057a:   5d                      pop    %rbp  //40057b:   c3                      retq

然后看下Test成员函数的反汇编,隐含着吧这个类实例的首地址作为第一个参数传了进来:

000000000040057c <_ZN4Test3funEv>:    int fun() {  //40057c:   55                      push   %rbp  //40057d:   48 89 e5                mov    %rsp,%rbp  //400580:   48 89 7d d8             mov    %rdi,-0x28(%rbp) // 把调用约定的参数1放到栈中        int a1 = a;        int b1 = b;  //400584:   48 8b 45 d8             mov    -0x28(%rbp),%rax  //400588:   8b 00                   mov    (%rax),%eax  //40058a:   89 45 ec                mov    %eax,-0x14(%rbp)        int c1 = c;  //40058d:   48 8b 45 d8             mov    -0x28(%rbp),%rax  //400591:   8b 40 04                mov    0x4(%rax),%eax  //400594:   89 45 f0                mov    %eax,-0x10(%rbp)        int d1 = d;  //400597:   48 8b 45 d8             mov    -0x28(%rbp),%rax  //40059b:   8b 40 08                mov    0x8(%rax),%eax  //40059e:   89 45 f4                mov    %eax,-0xc(%rbp)        int e1 = e;  //4005a1:   48 8b 45 d8             mov    -0x28(%rbp),%rax  //4005a5:   8b 40 0c                mov    0xc(%rax),%eax  //4005a8:   89 45 f8                mov    %eax,-0x8(%rbp)        return 100;  //4005ab:   48 8b 45 d8             mov    -0x28(%rbp),%rax  //4005af:   8b 40 10                mov    0x10(%rax),%eax  //4005b2:   89 45 fc                mov    %eax,-0x4(%rbp)    }  //4005b5:   b8 64 00 00 00          mov    $0x64,%eax};  //4005ba:   5d                      pop    %rbp  //4005bb:   c3                      retq

最后看下 main 函数如何在栈里定义个 Test对象并调用其方法:

int main(){  //4004ed:   55                      push   %rbp  //4004ee:   48 89 e5                mov    %rsp,%rbp    Test test(1, 2, 3, 4, 5);  //4004f1:   48 83 ec 20             sub    $0x20,%rsp  //4004f5:   48 8d 45 e0             lea    -0x20(%rbp),%rax  //4004f9:   41 b9 05 00 00 00       mov    $0x5,%r9d  // 调用约定里入参6, 从右向左参数压栈  //4004ff:   41 b8 04 00 00 00       mov    $0x4,%r8d  // 调用约定里入参5  //400505:   b9 03 00 00 00          mov    $0x3,%ecx  // 调用约定里入参4  //40050a:   ba 02 00 00 00          mov    $0x2,%edx  // 调用约定里入参3  //40050f:   be 01 00 00 00          mov    $0x1,%esi  // 调用约定里入参2  //400514:   48 89 c7                mov    %rax,%rdi  // 调用约定里入参1, test的地址  //400517:   e8 14 00 00 00          callq  //400530 <_ZN4TestC1Eiiiii>    test.fun();  //40051c:   48 8d 45 e0             lea    -0x20(%rbp),%rax  //400520:   48 89 c7                mov    %rax,%rdi  // test变量的地址作为了第一个参数  //400523:   e8 54 00 00 00          callq  //40057c <_ZN4Test3funEv>    return 0;  //400528:   b8 00 00 00 00          mov    $0x0,%eax}  //40052d:   c9                      leaveq  //40052e:   c3                      retq

通过反汇编C++编译后的ELF文件, 可以很容易看出,C++ 的class对象大小就是其成员变量组成对应接头的大小,class方法(成员函数)可以访问成员变量就是因为每个函数隐式的把对象的首地址传给了成员函数。知道了这点,就更加容易知道this指针的本质,也可以思考python的类self为什么满天飞。

转载地址:http://ifqli.baihongyu.com/

你可能感兴趣的文章
【unix网络编程第三版】阅读笔记(三):基本套接字编程
查看>>
同步与异步的区别
查看>>
IT行业--简历模板及就业秘籍
查看>>
JNI简介及实例
查看>>
DOM4J使用教程
查看>>
JAVA实现文件树
查看>>
linux -8 Linux磁盘与文件系统的管理
查看>>
linux 9 -文件系统的压缩与打包 -dump
查看>>
PHP在变量前面加&是什么意思?
查看>>
ebay api - GetUserDisputes 函数
查看>>
ebay api GetMyMessages 函数
查看>>
php加速器 - zendopcache
查看>>
手动12 - 安装php加速器 Zend OPcache
查看>>
set theme -yii2
查看>>
yii2 - 模块(modules)的view 映射到theme里面
查看>>
yii2 - controller
查看>>
yii2 - 增加actions
查看>>
网站加载代码
查看>>
php图像处理函数大全(缩放、剪裁、缩放、翻转、旋转、透明、锐化的实例总结)
查看>>
magento url中 uenc 一坨编码 base64
查看>>