美文网首页C++反汇编学习
关于C++ 的 this指针

关于C++ 的 this指针

作者: gradyfun | 来源:发表于2018-01-11 18:14 被阅读0次

关于this指针

在编程过程中,我们都使用过this指针,可是this指针究竟从何而来却很少有人知道,现在我们一起来看一下this指针的由来

测试代码如下:

class CTest
{
public:
    void SetNumber(int number)
    {
        m_nInt = number;
    }

    int m_nInt = 0;
};

int main(int argc, char *argv[])
{
    CTest test;
    test.SetNumber(5);
    printf("CTest : m_nInt = %d\n", test.m_nInt);

    system("pause");
    return 0;
}

对编译生成的程序进行反汇编:

main函数部分的汇编代码如下

int main(int argc, char *argv[])
{
01183CD0  push        ebp  
01183CD1  mov         ebp,esp  
01183CD3  sub         esp,0D0h  
01183CD9  push        ebx  
01183CDA  push        esi  
01183CDB  push        edi  
01183CDC  lea         edi,[ebp-0D0h]  
01183CE2  mov         ecx,34h  
01183CE7  mov         eax,0CCCCCCCCh  
01183CEC  rep stos    dword ptr es:[edi]  
01183CEE  mov         eax,dword ptr ds:[01188000h]  
01183CF3  xor         eax,ebp  
01183CF5  mov         dword ptr [ebp-4],eax  
    CTest test;
01183CF8  lea         ecx,[test]  
01183CFB  call        CTest::CTest (011811E5h)  
    test.SetNumber(5);
01183D00  push        5  
01183D02  lea         ecx,[test]  
01183D05  call        CTest::SetNumber (011810EBh)  
    printf("CTest : m_nInt = %d\n", test.m_nInt);
01183D0A  mov         esi,esp  
01183D0C  mov         eax,dword ptr [test]  
01183D0F  push        eax  
01183D10  push        1185858h  
01183D15  call        dword ptr ds:[1189118h]  
01183D1B  add         esp,8  
01183D1E  cmp         esi,esp  
01183D20  call        __RTC_CheckEsp (01181140h)  

    system("pause");
01183D25  mov         esi,esp  
01183D27  push        1185874h  
01183D2C  call        dword ptr ds:[1189110h]  
01183D32  add         esp,4  
01183D35  cmp         esi,esp  
01183D37  call        __RTC_CheckEsp (01181140h)  
    return 0;
01183D3C  xor         eax,eax  
}
01183D3E  push        edx  
01183D3F  mov         ecx,ebp  
01183D41  push        eax  
01183D42  lea         edx,ds:[1183D70h]  
01183D48  call        @_RTC_CheckStackVars@8 (01181087h)  
01183D4D  pop         eax  
01183D4E  pop         edx  
01183D4F  pop         edi  
01183D50  pop         esi  
01183D51  pop         ebx  
01183D52  mov         ecx,dword ptr [ebp-4]  
01183D55  xor         ecx,ebp  
01183D57  call        @__security_check_cookie@4 (0118101Eh)  
01183D5C  add         esp,0D0h  
01183D62  cmp         ebp,esp  
01183D64  call        __RTC_CheckEsp (01181140h)  
01183D69  mov         esp,ebp  
01183D6B  pop         ebp  
01183D6C  ret  

我们先简单理解一下上述的汇编代码:

01183CD0  push        ebp  
01183CD1  mov         ebp,esp  
01183CD3  sub         esp,0D0h  
01183CD9  push        ebx  
01183CDA  push        esi  
01183CDB  push        edi  

这是main函数中最开头的一部分代码,主要用于利用栈来保护现场,保存外部调用函数的基址(ebp寄存器中),并将其设置当前函数的基址,然后保存之后要用到的几个寄存器中的数据,这样当函数返回后,保证其调用函数可以继续正常向下执行。并将栈指针寄存器esp向栈低地址移动, 这主要用于开辟栈空间给当前函数块中使用。

01183CDC  lea         edi,[ebp-0D0h]  
01183CE2  mov         ecx,34h  
01183CE7  mov         eax,0CCCCCCCCh  
01183CEC  rep stos    dword ptr es:[edi]  

这部分代码主要用于初始化当前的栈空间,将esp 至 ebp 之间的内存全部初始化为0xCC(0xCC 表示当前内存暂未使用过)。

01183CEE  mov         eax,dword ptr ds:[01188000h]  
01183CF3  xor         eax,ebp  
01183CF5  mov         dword ptr [ebp-4],eax  

这段代码主要在基址指针上的前四个字节插入随机数,之后会利用这个随机数进行检查,防止栈溢出攻击。

    CTest test;
01183CF8  lea         ecx,[test]  
01183CFB  call        CTest::CTest (011811E5h)  

这里很明显开始调用CTest的构造函数,跟进去

CTest::CTest:
011811E5  jmp         CTest::CTest (011813D0h)  

发现这里执行的是一次跳转,再跟:

CTest::CTest:
011813D0  push        ebp  
011813D1  mov         ebp,esp  
011813D3  sub         esp,0CCh  
011813D9  push        ebx  
011813DA  push        esi  
011813DB  push        edi  
011813DC  push        ecx  
011813DD  lea         edi,[ebp-0CCh]  
011813E3  mov         ecx,33h  
011813E8  mov         eax,0CCCCCCCCh  
011813ED  rep stos    dword ptr es:[edi]  
011813EF  pop         ecx  
011813F0  mov         dword ptr [this],ecx  
011813F3  mov         eax,dword ptr [this]  
011813F6  mov         dword ptr [eax],0  
011813FC  mov         eax,dword ptr [this]  
011813FF  pop         edi  
01181400  pop         esi  
01181401  pop         ebx  
01181402  mov         esp,ebp  
01181404  pop         ebp  
01181405  ret  

这里可以看出是由编译器提供的默认构造函数的汇编指令,并在此处将m_nInt 初始化为0了,可见在类中初始化的变量,即使不是在初始化函数中初始化的成员变量,最后还是在构造函数中初始化。

    test.SetNumber(5);
01183D00  push        5  
01183D02  lea         ecx,[test]  
01183D05  call        CTest::SetNumber (011810EBh)  

lea 为加载地址指令
这里我们看一下test的地址


02.png

test的地址为0x0044fd74,在内存中此时成员m_nInt已被初始化为0了(如下图):

01.png

而基址指针ebp为0x0044fd80,比test的首地址大12(8+4),即对象的大小+随机数保存位置的内存大小。
这里是VS的优化后显示的结果,实际应该是 [test] 等价于 [ebp - 12]

所以在调用函数之前不但传入了5,还通过ecx (隐含地)传入了对象的首地址,即所谓的“this指针”

接着看函数内部的实现,以证明上述观点!

同理由此jmp跳转,直接看内部实现:

    void SetNumber(int number)
    {
01181420  push        ebp  
01181421  mov         ebp,esp  
01181423  sub         esp,0CCh  
01181429  push        ebx  
0118142A  push        esi  
0118142B  push        edi  
0118142C  push        ecx  
0118142D  lea         edi,[ebp-0CCh]  
01181433  mov         ecx,33h  
01181438  mov         eax,0CCCCCCCCh  
0118143D  rep stos    dword ptr es:[edi]  
0118143F  pop         ecx  
01181440  mov         dword ptr [this],ecx  
        m_nInt = number;
01181443  mov         eax,dword ptr [this]  
01181446  mov         ecx,dword ptr [number]  
01181449  mov         dword ptr [eax],ecx  
    }
0118144B  pop         edi  
0118144C  pop         esi  
0118144D  pop         ebx  
0118144E  mov         esp,ebp  
01181450  pop         ebp  
01181451  ret         4  

这段代码一直到 0118143D所在行都是日常任务,开辟并初始化栈空间。

0118143F  pop         ecx  
01181440  mov         dword ptr [this],ecx  

从这行开始真正的工作了,这里将外部传进来得对象首地址存进了 “this”中,我们查看一下this的首地址是多少
在当前ebp之上,保存着ecx中传入的0x0044fd74的值之处就是this指针的首地址。

03.png 04.png

从04图中可以看到0x0044FC90 即为栈上为this分配的内存的首地址,这里将ecx中保存的对象所在的首地址(即指针)保存在了为this所分配的内存中,指针本身占用4个字节,由于内存对齐,此处编译器分配了8个字节。0x0044FC90 + 8 正好等于ebp中保存的值。

01181443  mov         eax,dword ptr [this]  
01181446  mov         ecx,dword ptr [number]  
01181449  mov         dword ptr [eax],ecx  

再看看[number] 指代的是什么(当然是形参number啦,但实际反汇编时不会这么明显,我们来看看实际到底指代的是什么)

由前面代码推得,在进入函数前,5已经压人栈中,所以5的地址必然大于当前的ebp的值,由图4可见number的地址为ebp+8。

ebp为首地址的指针为调用该函数的函数的栈的基址。
ebp + 4 为调用该函数处的下一行指令的的地址,用于ret 返回时使用。

相关文章

  • C++知识点

    C++基本方法: C++ memcpy C++基本特性: C++引用(vs指针) C++指针 C++封装: 将...

  • 关于C++ 的 this指针

    关于this指针 在编程过程中,我们都使用过this指针,可是this指针究竟从何而来却很少有人知道,现在我们一起...

  • lc25 关于链表

    复习知识点:1)链表反转2)关于指针。理解C++中指针传参,大概指针传参也是传的形参(like int数字),但是...

  • const理解

    关于const *和* const的理解,可以参考[C C++ OC指针常量和常量指针区别]这篇文章。 该篇文章中...

  • 浅谈C/C++的指针,引用

    前言 随手也写一下关于ndk开发中关于引用和指针的大致用法,就不上代码了,简单的写一下。 关于指针* C++可以看...

  • C++ 指针常量、常量指针和常指针常量

    参考:C++ 指针常量、常量指针和常指针常量

  • Java基础

    Java和C++的区别?a. Java没有指针。c++可以通过指针直接操作内存,但这个动作是危险的,指针引起的操作...

  • C++ 指向类的指针

    原文地址:C++ 指向类的指针 一个指向 C++ 类的指针与指向结构的指针类似,访问指向类的指针的成员,需要使用成...

  • Jna send pointer pointer to c++

    目的: 有这样一个需求,java通过jna传递指针数组给c++,或者指针的指针 解决方案: c++ : 声明 vo...

  • C++基础

    C++ 值传递、指针传递、引用传递详解C++中引用传递与指针传递区别 引用传递和指针传递的区别 引用的规则:(1)...

网友评论

    本文标题:关于C++ 的 this指针

    本文链接:https://www.haomeiwen.com/subject/mogznxtx.html