首页 > 编程语言 >C++逆向分析——this指针

C++逆向分析——this指针

时间:2023-04-05 21:01:46浏览次数:41  
标签:逆向 struct int C++ Plus Student 结构 指针

this指针

概述

C++是对C的拓展,C原有的语法C++都支持,并在此基础上拓展了一些语法:封装、继承、多态、模板等等。C++拓展新的语法是为了让使用更加方便、高效,这样就需要编译器多做了很多事情,接下来我们就需要一一学习这些概念。

封装

之前我们学习过结构体这个概念,那么结构体可以做参数传递吗?我们来看一下如下代码:

struct Student { int a; int b; int c; int d; };   int Plus(Student s) { return s.a + s.b + s.c + s.d; }   void main() { Student s = {1, 2, 3, 4}; int res = Plus(s); return; }

上面这段代码是定义一个结构体,然后将该结构体传入Plus函数(将结构体成员相加返回),那么问题来了,结构体它是否跟数组一样,传递的是指针呢?来看一下反汇编代码:

images/download/attachments/12714553/image2021-3-28_0-9-23.png

可以很清晰的看见,结构体作为参数传递时栈顶(ESP)提升了0x10(16个字节,也就是结构体的四个成员【int】的宽度),而后将ESP的值给了EAX,再通过EAX(ESP)将结构体的成员传入函数,结构体成员从左到右依次从栈顶向下复制进入堆栈。

也就是说当我们将结构体作为参数传递时与我们传整数什么的是没有本质区别的,唯一的区别就是传递结构体时不是使用的push来传递的,而是一次性的提升堆栈,然后mov赋值。

虽然我们可以使用结构体进行传参,但是这也存在一个问题,就是当我们使用结构体传参时,假设结构体有40个成员,那么就存在着大量的内存被复制,这样效率很低,是不推荐使用的

那如果非要这样使用该怎么办呢?我们可以使用指针传递的方式来,修改一下代码:

struct Student { int a; int b; int c; int d; };   int Plus(Student* p) { return p->a + p->b + p->c + p->d; }   void main() { Student s = {1, 2, 3, 4}; int res = Plus(&s); return; }

images/download/attachments/12714553/image2021-3-28_0-24-53.png

这样我们就可以使用指针的方式来避免内存的重复使用,效率更高。

可能很多人看到这就很疑惑了,那这跟C++有什么关系呢?我们之前说过C++和C的本质区别,就是编译器替代我们做了很多事情;别着急,慢慢来看。

我们使用指针优化过的代码,实际上还是存在小缺陷的,当结构体成员很多的时候,我们在Plus函数体内就要用指针的调用方式,一堆成员相加...

那么是否可以让我们调用更加简单,更加方便呢?如下代码就可以:

struct Student { int a; int b; int c; int d;   int Plus() { return a + b + c + d; } };   void main() { Student s = {1, 2, 3, 4}; int res = s.Plus(); return; }

将函数放在结构体内,就不需要我们再去写传参、再去使用指针的调用方式了,因为这些工作编译器帮我们完成了,而本质上这与指针调用没有区别:

images/download/attachments/12714553/image2021-3-28_0-37-54.png

而这种写法就是C++的概念:封装;也就是说将函数写在结构体内的形式就称之为封装,其带来的好处就是我们可以更加方便的使用结构体的成员。

讲到了封装,我们就要知道另外两个概念:

  1. :带有函数的结构体,称为类;

  2. 成员函数:结构体里的函数,称为成员函数

    1. 函数本身不占用结构体的空间(函数不属于结构体

    2. 调用成员函数的方法与调用结构体成员的语法是一样的 → 结构体名称.函数名()

this指针

之前我们学过了封装,如下代码:

struct Student { int a; int b; int c; int d;   int Plus() { return a + b + c + d; } };   void main() { Student s = {1, 2, 3, 4}; int res = s.Plus(); return; }

其对应的反汇编代码如下:

images/download/attachments/12714553/image2021-3-28_0-37-54.png

可以看见我们使用s.Plus()的时候,传递的参数是一个指针,这个指针就是当前结构体的地址,这个指针就是this指针。(通常情况下编译器会使用ecx来传递当前结构体的指针)

那么当我们将Plus函数修改成无返回值,不调用结构体成员后,这个指针还会传递过来么?

struct Student { int a; int b; int c; int d;   void Plus() {   } };   void main() { Student s = {1, 2, 3, 4}; s.Plus(); return; }

我们看下反汇编代码,发现指针依然会传递过来:

images/download/attachments/12714553/image2021-3-28_0-51-7.png

那也就是说this指针是编译器默认传入的,通常会通过ecx进行参数的传递,不管你用还是不用,它都存在着

既然this指针会作为参数传递,我们是否也可以直接使用这个指针呢?答案是可以的:

struct Student { int a; int b;   void Init(int a, int b) { this->a = a; this->b = b; }   };

我们在结构体的成员函数内使用this这个关键词就可以调用了,如上代码所示。

那么this指针有什么作用呢?我们可以看下如下代码:

struct Student { int a; int b;   void Init(int a, int b) { a = a; b = b; }   };   void main() { Student s; s.Init(1,2); return; }

这段代码我们要实现的就是,使用成员函数初始化成员的值,但是实际运行却不符合我们的预期:

images/download/attachments/12714553/image2021-3-28_1-29-45.png

跟进反汇编代码发现,这里就是将传入的参数赋值给了参数本身,并没有改变成员的值,这是因为编译器根本不知道你这里的a到底是谁,所以我们就需要借助this指针来实现:

#include <stdio.h>   struct Student { int a; int b;   void Init(int a, int b) { this->a = a; this->b = b; }   void Print() { printf("%d %d", this->a, this->b); }   };   void main() { Student s; s.Init(1,2); s.Print(); return; }

为了方便,添加一个成员函数,用于打印输出成员的值:

images/download/attachments/12714553/image2021-3-28_1-37-52.png

可以看见,这里成功进行初始化了。

总结:

  1. this指针是编译器默认传入的,通常会使用ecx进行参数的传递

  2. 成员函数都有this指针,无论是否使用

  3. this指针不能做++ --等运算,也不可以被重新赋值

  4. this指针不占用结构体的宽度

 

this指针和函数都不占用struct的空间,我们验证下:

#include <cstdio>

struct A {
    char* hello() {
        return "hi";
    }
};

int main() {
    A a;
    printf("empty struct size=%d\n", sizeof(a));
}

 

输出为1。

所以可以知道,没有任何成员变量的struct大小为1.

标签:逆向,struct,int,C++,Plus,Student,结构,指针
From: https://www.cnblogs.com/bonelee/p/17290879.html

相关文章

  • 单链表进阶OJ版--->随机指针问题
    朋友们,晚上好!!今天,推出一篇单链表的随机指针问题!!相较于之前的链表OJ题,本期的链表难度有所提升!!下面请看题:>有一个链表,链表每个结点额外增加一个随机指针random,并且随机指针可以指向链表的任何结点以及空结点至于本题的要求是:复制带随机指针的链表如下图所示:>本题的难度,大致......
  • c++ std::string_view
    std::string_view系C++17标准发布后新增的内容。C++17中我们可以使用std::string_view来获取一个字符串的视图,字符串视图并不真正的创建或者拷贝字符串,而只是拥有一个字符串的查看功能。std::string_view比std::string的性能要高很多,因为每个std::string都独自拥有一份字符串的拷......
  • Auto Chess (双指针, 极角排序)
    题目大意:释放一个45都的技能去尽可能消灭更多的敌人(在一个平面里面)  思路:技能是无线长的,于是抛弃无用信息,只保留斜率即可然后利用双指针,或者二分去做即可 ......
  • c++ struct和class
    c++中对象通常用struct和class表示。在c语言中,struct用来创建结构体,结构体中只能包含基本数据成员。而在c++中struct能包含成员函数,能实现多态(继承)。c++中struct和class的功能十分类似,但是在面向对象的过程中class的使用较多,两者的区别如下:struct与class的主要区别在于默认......
  • C++函数库——全排列
    全排列,顾名思义,对一个无序数组或者有序数组写出其对应的所有组合,实则为从当前数组顺序开始,排列出所有比当前序列大(默认)或者小的所有组合,所以如果初始为无序数组,则得到的结果并非所有组合1.next_permutation,获取下一个排列结果,及获取比当前序列小的下一个序列1#include<iost......
  • c++ std::variant
    std::variant是c++17引入的一个类型,其作用类似于C语言中的Union,但是比Union的功能强大的多。C语言中一个联合体Union可以储存多种类型数据,但缺点有很多。比如:1没有可用的方法来判断Union中真实储存的类型,获取值时也是内存拷贝的结果,可能会存在问题。这就只能靠程序员人脑保证......
  • 2.Visual studio的使用和C++的基础
    visualstudio的基础介绍C++入门编程C++关键字、标识符C++基本数据类型变量的定义visualstudio的基础介绍解决方案资源管理器 也可以在”视图”中找到”解决方案资源管理器” “.h”:头文件;“.cpp”:源文件添加新建项,选择C++语言  运行程序,或"ctrl+F5“ ......
  • C++中的左值和右值
    左值与右值左值和右值有如下3个规则:左值和右值都是表达式。左值是对象定位器,理论上左值指示指示一个对象。右值是一个临时值,其他位置无法访问这个值,通常情况下在语句执行完成后右值就被丢弃。http://c.biancheng.net/view/281.html左值:可以进行取地址的称为左值。右值:不......
  • C++复习-第一天
    C++支持完全面向对象的程序设计,包括面向对象开发的四大特性:封装;抽象;继承;多态。C++封装 C++抽象 C++继承 C++多态多态的实现方式分为三块:重载,重写,重定义。1.重载是指在同一作用域下,具有相同函数名称不同的参数类型,或不同的参数个数,或不同的返回类型。2.重写是指在......
  • 逆向——汇编中的位运算
     汇编中的移位指令1、算术移位指令SAL(ShiftArithmeticLeft):算术左移SAR(ShiftArithmeticRight):算术右移格式:SAL/SARReg/Mem,CL/Imm即算数移位指令后面的第一个操作数是寄存器或者内存;第二个操作数是寄存器或者立即数SALeax,2SALax,1SALal,3​​举例:SAR......