C++反汇编学习笔记5——结构体和类2

两年前写的,欢迎大家吐槽!

转载请注明出处。

3.静态数据成员

CStatic类具有静态的数据成员,现在来看一下它与上面提到过的类有什么不同:

class CStatic

{

public:

    void ShowNumber()

    {

        printf("m_nInt= %d , m_snInt = %d", m_nInt, m_snInt);

    }

 

    static int m_snInt;

    int m_nInt;

};

   224:      CStatic staticOne;

   225:      printf("%08x\r\n",&staticOne.m_nInt);

0042B12E  lea        eax,[staticOne]    ;非静态数据,和前面说的一样,得到this指针

0042B131  push       eax 

0042B132  push       offset string "CTest : %d\r\n" (472F78h) 

0042B137  call       @ILT+3895(_printf) (426F3Ch) 

0042B13C add         esp,8 

   226:      printf("%08x\r\n",&staticOne.m_snInt);

0042B13F push        offset CStatic::m_snInt(482000h)  ;静态数据成员,直接使用立即数间接寻址找到数据,无需this指针

0042B144  push       offset string "CTest : %d\r\n" (472F78h) 

0042B149  call       @ILT+3895(_printf) (426F3Ch) 

0042B14E  add        esp,8 

   227:      CStatic staticTwo;

   228:      printf("%08x\r\n",&staticTwo.m_nInt);

0042B151  lea        eax,[staticTwo]  ;与上同理

0042B154  push       eax 

0042B155  push       offset string "CTest : %d\r\n" (472F78h) 

0042B15A call        @ILT+3895(_printf) (426F3Ch) 

0042B15F add         esp,8 

   229: printf("%08x\r\n",&staticTwo.m_snInt);

0042B162  push       offset CStatic::m_snInt (482000h) ;取同一地址内的数据

0042B167  push       offset string "CTest : %d\r\n" (472F78h) 

0042B16C call        @ILT+3895(_printf) (426F3Ch) 

0042B171  add        esp,8 

 上面的代码定义了两个CStatic变量,可以看到使用静态数据成员的方式与非静态成员的方式大不相同,静态成员就相当于是全局变量,所以无需使用this指针。同时除了这些还可以看到不同的对象共享同一个静态数据成员,说明静态数据成员和对象无关,只和类有关。下面是两个对象的数据成员的地址。

除了在调用方式上不同,静态数据成员的所在地址空间也是不同的,这从上面的图上也可以看出来。再来看看拥有静态数据成员的结构的大小会有什么不同:

CStatic Static;

   232:      int nSize = sizeof(Static);

0042B12E  mov        dword ptr [nSize],4

这里编译器使用了常量扩散,说明Static对象的大小为4字节,静态数据成员并不参与对象长度的计算。

静态数据成员在反汇编代码中很难被识别,其展示形态和全局变量完全相同,所以可实际情况还原成高级代码,酌情处理。

4.对象作为函数参数

对象作为函数的参数进行传递与数组进行传递的方式不同,它是将其所有的数据进行备份,然后将备份的数据传入函数中作为形参进行处理。除了双精度浮点型之外其他基本数据类型都能通过栈中的一个元素来实现赋值传参的操作。但是各个对象的长度不定,所以必须采取合适的传参方式才能顺利地将对象传进函数。下面就来看一个例子:

CFunTest类:

class CFunTest

{

public:

    int m_nOne;

    int m_nTwo;

};

main函数:

   243:      CFunTest FunTest;

   244:      FunTest.m_nOne = 1;

0042B0FE  mov        dword ptr [FunTest],1  ;相当于mov  [ebp-C],1

   245:      FunTest.m_nTwo = 2;

0042B105  mov        dword ptr [ebp-8],2 

   246: ShowFunTest(FunTest);

0042B50F mov         eax,dword ptr[ebp-8] 

0042B10F push        eax  ;m_nTwo入栈

0042B110  mov        ecx,dword ptr [FunTest] 

0042B113  push       ecx  ;m_nOne入栈

0042B114  call       ShowFunTest (426D66h) 

0042B119  add        esp,8 

ShowFunTest函数:

   106: void ShowFunTest(CFunTest FunTest)

   107: {

   109: printf("%d%d \r\n", FunTest .m_nOne, FunTest.m_nTwo);

0042AF1E  mov        eax,dword ptr [ebp+0Ch] 

0042AF21  push       eax 

0042AF22  mov        ecx,dword ptr [FunTest]  ;相当于mov  ecx,[ebp+8]

0042AF25  push       ecx 

0042AF26  push       offset string "%d %d \r\n" (472EE8h) 

0042AF2B  call       @ILT+3895(_printf) (426F3Ch) 

0042AF30  add         esp,0Ch 

   110: }

CFunTest类比较小,只有两个int型参数,所以它作为参数时只需要两次入栈即可。同时也可以看到类对象中的数据成员的入栈顺序:先定义的后入栈,后定义的先入栈。但是当类的体积过大或者其中定义有数组时,情况又是如何?再看一个例子:

首先是main函数:

   243:      CFunTest FunTest;

   244:      FunTest.m_nOne = 1;

0042B138  mov        dword ptr [ebp-30h],1 

  245:      FunTest.m_nTwo = 2;

0042B13F mov         dword ptr[ebp-2Ch],2 

   246:      strcpy(FunTest.m_szName, "Name");

0042B146  push       offset string "0x%08x\r\n" (472F78h) 

0042B14B  lea        eax,[ebp-28h]  ;取出数组的首地址

0042B14E  push       eax 

;其实上这里不对,应该改为0042B14F  call j_strcpy,这里应该是VS2010反汇编器的错误

0042B14F call        CTest::SetNumber(42712Bh) 

0042B154  add        esp,8 

   247: ShowFunTest(FunTest);

0042B157  sub        esp,28h  ;将栈顶太高28h用于存储类对象中的数据

0042B15A mov         ecx,0Ah  ;给循环计数器ecx赋值0Ah,就是要循环10次

0042B15F lea         esi,[ebp-30h]  ;获取对象的首地址

0042B162  mov        edi,esp  ;将当前栈顶地址赋值给edi

0042B164  rep movs   dword ptr es:[edi],dword ptr [esi] ;将对象中的数据分10次存入栈中,每次4字节

0042B166  call       ShowFunTest (426D66h)  ;调用类中的函数

0042B16B  add        esp,28h  ;栈平衡操作

下面再来看一下类中的ShowFunTest函数(该函数仅有输出函数这一行代码):

   109: printf("%d%d %s\r\n", FunTest .m_nOne, FunTest.m_nTwo, FunTest.m_szName);

0042AF1E  lea        eax,[ebp+10h]  ;对象中的数组首地址入栈

0042AF21  push       eax 

0042AF22  mov        ecx,dword ptr [ebp+0Ch]  ;m_nTwo入栈

0042AF25  push       ecx 

0042AF26  mov        edx,dword ptr [FunTest]  ;m_nOne入栈

0042AF29  push       edx 

0042AF2A push        offset string "%d%d %s\r\n" (472EE8h) 

0042AF2F call        @ILT+3895(_printf) (426F3Ch) 

0042AF34  add        esp,10h 

从前面的main函数调用类成员函数的代码可以看到,由于传递的参数是类的对象,并且对象中还有数组的存在,所以不能简单的直接入栈,而是需要将栈顶指针太高,以便拥有足够的空间存储下较大的数据。这里太高栈顶使用的语句是sub esp,28h,但是书上使用的语句是add esp,FFFFFFE0h,这是一样的。加上FFFFFFE0h相当于减去20h,但是还有8字节空间呢,这里书上在调用完strcpy函数之后并没有进行栈平衡,而是继续利用之前的8字节空间,这样可以增加代码的效率。但是VS2010编译器没有这样做,也不知为何。

上面这种情况是没有定义构造函数和析构函数的情况,现在来看一看定义了这两个函数之后会出现什么问题:

class CMyString

{

public:

    CMyString(){

        m_pString = newchar[10];

        if(m_pString == NULL){

            return;

        }

        strcpy(m_pString, "Hello");

    }

    ~CMyString(){

        if (m_pString!= NULL){

            deletem_pString;

            m_pString = NULL;

        }

    }

    char *GetString(){

        returnm_pString;

    }

private:

    char *m_pString;

};

//在main函数中调用的函数

void ShowMyString(CMyString MyStringCpy)

{

    printf(MyStringCpy.GetString());

}

//main函数

   251:      CMyString MyString;

004284DD  lea        ecx,[ebp-14h]  ;取出对象的首地址

004284E0  call       CMyString::CMyString (4266FEh)  ;调用构造函数

;记录一个作用域内该类对象的个数,具体用法后面介绍

004284E5  mov        dword ptr [ebp-4],0 

   252:      ShowMyString(MyString);

004284EC  mov        eax,dword ptr [ebp-14h] 

004284EF  push       eax  ;将对象首地址入栈作为函数的参数

004284F0  call        ShowMyString (426CA8h) 

004284F5  add         esp,4 

下面是构造函数:

   117: CMyString(){

004286FF  pop        ecx 

00428700  mov        dword ptr [ebp-8],ecx 

   118:      m_pString = new char[10];

00428703  push       0Ah 

00428705  call       operator new[] (426636h) 

0042870A  add         esp,4 

0042870D  mov        dword ptr [ebp-0D4h],eax 

00428713  mov        eax,dword ptr [this] 

00428716  mov        ecx,dword ptr [ebp-0D4h] 

0042871C  mov         dword ptr [eax],ecx 

   119:      if(m_pString == NULL){

0042871E  mov        eax,dword ptr [this] 

00428721  cmp        dword ptr [eax],0 

00428724  jne        CMyString::CMyString+48h (428728h) 

   120:          return;

00428726  jmp        CMyString::CMyString+5Bh (42873Bh) 

   121:      }

   122:      strcpy(m_pString,"Hello");

00428728  push       offset string "Hello" (474F04h) 

0042872D  mov        eax,dword ptr [this] 

00428730  mov        ecx,dword ptr [eax] 

00428732  push       ecx 

00428733  call       @ILT+1620(_strcpy) (426659h) 

00428738  add        esp,8 

   123: }

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值