逆向分析C++类的本质

面向对象的语言中,类这种语言特性是最基本也是最重要的东西。这篇博客记录下从汇编角度去理解类的本质是什么。创建一个对象的本质又是什么。

一.C语言中的结构体和C++的类有啥区别

我们知道在C语言中,有语言本身自带的一些内置类型。比如int,char,float等等。这些都是C语言编译器做好规划的,比如说我现在创建一个int类型的变量,那么编译器就会在为我们分配一个4字节的内存大小,并标识这块内存是整形变量。其实创建变量的本质就是在内存开辟适当大小的空间供数据存放。那么问题就来了,内置类型都是事先做好设定的。并不能满足用户的多样性数据存储。因此C语言就有了用户自定义类型-struct结构体。结构体的本质就是能让用户创建自定义的各种变量的集合体以此来满足用户对数据存储和管理的一种办法。比如我现在想创建如下结构体:

    struct Stduent {char name[10];int age;int Class_num;};

那么本质就是在内存中会创建一个如图的内存:


因此这就是为什么在计算结构体的大小的时候,是按照每个类型的大小相加的总共。讲明白了这个接下来就是讲C++的类。C++的类跟C语言中的结构体本质上是一个东西。但是他会多一点东西。下面分点讲解:

1.C语言中,结构体中只有属性也就是数据类型,没有函数也不能定义函数,而在C++中类中是可以定义方法的(也就是函数)。

2.C语言中,结构体中权限默认是public类型的,而C++中类的权限默认是private。

3.C语言中,创建一个结构体仅仅只是开辟了一块存放结构体大小的内存,而在C++中,创建一个对象会自动调用构造函数,释放的时候会自动调用析构函数。

以上就是C的结构体和C++类的基本区别和相似点。

结构体跟类一样,在没初始化的时候,都类似于模板,存在于数据区。本质是告诉编译器有这么个模板,用户需要创建的时候,根据这个模板来创建。因此我们的结构体和类如果被创建会有两个地方存储,一个是栈,一个就是堆,取决于你的创建方式。如果是在函数内部初始化那么相当于局部变量,是在栈上布局的。如果是采用malloc或者new,那么这些结构体和类的初始化数据将会布局在堆上。这是一个很重要的点。


二.C++逆向分析类

既然C结构体和类本质上是一个东西,那么它在内存是怎么布局的呢?我们写一个小的demo去调试分析下:

#include<iostream>
#include<string>
using namespace std;class Student {public:string name;int age;void  ShowStudent(){cout<<"name="<< name <<   "age= "<< age <<endl;}};
int main(){Student s1;s1.name="ChenWeiXin";s1.age=2;s1.ShowStudent();return 0;}

上面我们讲了,C++的类中不仅有属性,还有方法。一般我们称之为成员属性,成员方法。其实就是数据类型和函数的组合体。

现在我们逆向分析对象的创建过程和变量赋值,成员方法调用和对象销毁的过程。这里我是用g++编译的,不同的编译器编译出来的效果和特性都是不一样的,主要区别是一些细节上的问题。


我们看到当我们即将创建一个对象的时候,他会有三步操作。

1.lea rax,[rbp-0x40]   //取rbp-0x40的地址赋值给rax

2.mov rdi,rax       //将rax的值赋值给rdi

3.call Student:Student()      //调用Student对象的构造函数


首先我们要知道一件事情,就是Linux上的x64函数调用规则,顺序是rdi,rsi,rdx,rcx,r8,r9--栈。

也就是说如果参数低于6个用寄存器传参,高于6个会在栈上。正向学C++的时候,我们知道,在调用成员函数的时候,默认第一个参数其实是this指针。那么此时的rdi就是this指针,因为我们的源程序中没有自定义的构造函数,因此此时调用的是默认构造函数,没有其他参数,所以只要一个this指针参数。那么this指针是啥呢?其实它本质就是对象的首地址。[rbp-0x40]这个地址就是栈地址所以我们可以看到在main函数这个作用域中,s1对象在栈上布局的。

      

我们接着往下看:


此时我们跟着进来了这个构造函数,简单看下默认构造函数干了什么事情。好像啥也没干其实,调用了一个函数我也不知道干啥的,看上去是初始化一些啥的反汇编看了下:


大概猜测是跟字符串类的有关。

紧接着就来到了我们的对成员属性赋值的操作:


这里又干了什么呢,我们看到还是会将对象的首地址也即是this指针当第一个参数传递,第二个参数传的是rsi。盲猜是我们的字符串的地址。因为接下来调用的这个函数就是C++对字符串做赋值操作的一个函数:


数值赋值就相当简单:

因此我们明白了,对象的首地址在rbp-0x40的位置,也就是name的位置,rbp-0x20的位置存放的是我们的age。


这个age这里我不知道为啥还有0x55500000的存在。但是这个确实就是age字段,而且里面有些奇奇怪怪的东西,我也搞不懂(研究了比较久age字段后面这个奇怪的数字)不过大致布局我们是清晰的。


这里我猜测是为了8字节对齐。这样数据读取更快。紧接着我们又看到调用了成员方法:


还是老规矩,传入this指针,然后call我们的ShowStudent函数。具体我就不跟进去了。最后程序即将结束的时候:


依然是传入this指针,然后调用析构函数。这里的析构函数依然是默认的,因此也是啥也没干其实。

整个程序就走完了。通过上述分析,我们验证了如下几点:

1.对象的创建和销毁的时候,会自动调用构造函数和析构函数,即使不提供也会调用默认的。如果用户提供将会调用提供好的。

2.对象创建后对象中的成员属性如果是在函数中做为局部变量将会创建在栈上,如果用new创建将会在堆上。生命周期是不一样的。(堆上可以自行验证)

3.对象中的成员方法在单独的代码区。我一开始以为是会将成员方法的起始地址存放在属性字段的后面,但是好像不是那么回事。(我学java的时候记得是这么说的)。但是C++中好像不是这么做的。具体有待研究。我查资料说是虚函数是这么做的。到时候验证下。

以上就是我自己对类的一个理解。如果有错误欢迎评论指出。感激不尽。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/417173.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoot 项目中后端实现跨域的5种方式!!!

文章目录 SpringBoot 项目中后端实现跨域的5种方式&#xff01;&#xff01;&#xff01;一、为什么会出现跨域问题二、什么是跨域三、非同源限制四、Java后端 实现 CORS 跨域请求的方式1、返回新的 CorsFilter(全局跨域)2、重写 WebMvcConfigurer(全局跨域)3、使用注解 (局部跨…

用Photoshop来制作GIF动画

录了个GIF格式的录屏文件&#xff0c;领导让再剪辑下&#xff0c;于是用Photoshop2023&#xff08;PS版本低至CS6操作方式一样&#xff09;进行剪辑&#xff0c;录屏文件有约1400帧&#xff0c;由于我处理的帧数太多&#xff0c;PS保存为GIF格式时&#xff0c;还是挺耗时的&…

基于Redisson的RAtomicLong实现全局唯一工单号生成器

最近几年&#xff0c;我一直从事的是运营平台业务开发。每天&#xff0c;我们都需要处理大量的工单配置工作。为了生成工单号&#xff0c;我们建立了一张专用的数据库表&#xff0c;用于记录和生成工单号。每次创建工单时&#xff0c;我们会查询这张表&#xff0c;根据年份字段…

视频图像的color range简介

介绍 研究FFmpeg发现&#xff0c;在avcodec.h中有关于color的解释&#xff0c;主要有四个属性&#xff0c;primaries、transfer、space和range。 color primaries&#xff1a; 基于RGB空间对应的绝对颜色XYZ的变换&#xff0c;决定了最终三原色RGB分别是什么颜色&#xff1b;…

Linux上软件安装

软件安装常见方式 二进制发布包 软件已经针对具体平台编译打包发布&#xff0c;只要解压&#xff0c;修改配置即可。 RPM包 软件已经按照redhat的包管理工具规范RPM进行打包发布&#xff0c;需要获取到相应的软件RPM发布包&#xff0c;然后用RPM命令进行安装&#xff0c;但…

开源模型应用落地-qwen模型小试-入门篇(五)

一、前言 这是关于qwen模型入门的最后一篇文章。主要介绍如何使用魔搭的API在本地调用qwen模型。此外&#xff0c;通过阅读这一系列的文章&#xff0c;如果您真的亲自动手实践过&#xff0c;我相信您已经掌握了qwen模型的基本使用方法。 二、术语 2.1. ModelScope社区 打造下一…

【运维】安装双系统之后,如何删除主硬盘的Linux的引导,图文教程

前置条件&#xff1a;已经安装了windows10系统和Linux系统&#xff0c;而且windows10系统是C盘主要盘&#xff0c;Linux系统是安装在别的硬盘上&#xff0c;这个时候C盘主要盘里面的引导分区里是由Linux的引导的&#xff0c;所以打开电脑之后才能让你选是使用windows系统还是使…

pyqt5+python子域名扫描程序

import sysfrom PyQt5 import uic from PyQt5.QtWidgets import * #requests库内置了不同的方法来发送不同类型的http请求 import requests#BS主要功能是从网页抓取数据&#xff0c;提供一些简单的、python 式的函数用来处理导航、搜索、修改分析树等功能 from bs4 import Beau…

ValueError: Unable to read workbook: could not read strings from data.xlsx解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

flutter在windows系统上实现左右水平滑动问题

在个问题在github也有记录&#xff1a;https://github.com/flutter/flutter/issues/105095 就是flutter使用listview等可以滑动的组件来左右滚动的时候&#xff0c;不能正常工作&#xff0c;也就是无效&#xff0c;所以下面大家开始讨论这个问题。 翻阅大家讨论的内容&#x…

交叉编译工具 aarch64-linux-gnu-gcc 的介绍与安装

AArch64 是随 ARMv8 ISA 一起引入的 64 位架构&#xff0c;用于执行 A64 指令的计算机。而且在 AArch64 状态下执行的代码只能使用 A64 指令集。&#xff0c;而不能执行 A32 或 T32 指令。但是&#xff0c;与 AArch32 中不同&#xff0c;在64位状态下&#xff0c;指令可以访问 …

初识Mybatis框架技术(上)

文章目录 MyBatis简介MyBatis特性搭建MyBatis 核心配置文件详解 MyBatis简介 MyBatis特性 MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集MyBatis可以使用简单的XML或注解用于配置和原始…