类反汇编

类的内存地址对齐

  参考之前写的一个博客https://www.cnblogs.com/Sna1lGo/p/14088339.html

类的this指针

  每个有关于对象里面的调用,其实都用到了this指针,this指针是一个对象的首地址,然后利用首地址的偏移来访问对象中的成员或者方法(函数)

对象中的函数

  在对象中调用函数的时候,其实是采用了ecx来保存this指针,然后来调用处理。这样的函数约定也称为thiscall

public:
    int m_nInt;
};
int main()
{
00221950  push        ebp  
00221951  mov         ebp,esp  
00221953  sub         esp,0D0h  
00221959  push        ebx  
0022195A  push        esi  
0022195B  push        edi  
0022195C  lea         edi,[ebp-0D0h]  
00221962  mov         ecx,34h  
00221967  mov         eax,0CCCCCCCCh  
0022196C  rep stos    dword ptr es:[edi]  
0022196E  mov         eax,dword ptr [__security_cookie (022A004h)]  
00221973  xor         eax,ebp  
00221975  mov         dword ptr [ebp-4],eax  
00221978  mov         ecx,offset _36AAE6AE_源@cpp (022C029h)  
0022197D  call        @__CheckForDebuggerJustMyCode@4 (022132Ah)  
    CTest Test;
    Test.SetNumber(5);
00221982  push        5  
00221984  lea         ecx,[Test]  
00221987  call        std::endl<char,std::char_traits<char> > (022116Dh)  
    printf("CTest : %d\r\n", Test.m_nInt);
0022198C  mov         eax,dword ptr [Test]  
0022198F  push        eax  
00221990  push        offset string "CTest : %d\r\n" (0227BCCh)  
00221995  call        _printf (02213CAh)  
0022199A  add         esp,8  
    return 0;
0022199D  xor         eax,eax  

}

  类中函数调用的注意点:类中的函数用的是thiscall,就是先用ecx来传入this指针,然后别的和__stdcall是一样的,但是thiscall并不是一个关键字,所以如果在类的成员函数里面添加了关键字,会用关键字的函数约定来调用

    CTest Test;
    Test.SetNumber(5);
00ED1982  push        5  
00ED1984  lea         eax,[Test]  
00ED1987  push        eax  
00ED1988  call        std::endl<char,std::char_traits<char> > (0ED13DEh)  

  这里的成员函数setNnumber采用__stdcall后就是利用栈来传参了

对象中的数据访问

  要访问对象的数据,也要使用this指针然后偏移来访问

类中的静态数据成员

  当类中有了static静态数据成员定义后,虽然是在类中定义的,但是它和静态局部变量类似,存放的位置和全局变量一直,只是增加了作用域的检查。都是一个含有作用域的全局变量。当程序被加载时操作系统就把执行文件的数据读到了内存单元中,静态数据成员就存在了。而这个时候还没有实例对象产生,静态数据成员不属于某一对象,与对象是一对多的关系,仅仅和类有关,与对象无关。所以在计算类和对象的大小的时候,并不把静态成员纳入其中

对象作为函数参数

  对象作为函数的参数时,会先将对象中的所有数据进行备份(复制),将复制的数据作为形参传递到调用函数中使用。

  传参的顺序为从下到上,最先定义的最后入栈。

当对象成员比较多的时候:

  这个时候对象成员比较多,利用栈来吧所有数据入栈就很麻烦,所以采用一个直接入栈对象的首地址,然后进行串指令赋值给栈空间

    int m_nInt1;
    int m_nInt2;
    char mChar1[32] = "test";
};
int main()
{
00D84A60  push        ebp  
00D84A61  mov         ebp,esp  
00D84A63  sub         esp,0F4h  
00D84A69  push        ebx  
00D84A6A  push        esi  
00D84A6B  push        edi  
00D84A6C  lea         edi,[ebp-0F4h]  
00D84A72  mov         ecx,3Dh  
00D84A77  mov         eax,0CCCCCCCCh  
00D84A7C  rep stos    dword ptr es:[edi]  
00D84A7E  mov         eax,dword ptr [__security_cookie (0D8A004h)]  
00D84A83  xor         eax,ebp  
00D84A85  mov         dword ptr [ebp-4],eax  
00D84A88  mov         ecx,offset _36AAE6AE_源@cpp (0D8C029h)  
00D84A8D  call        @__CheckForDebuggerJustMyCode@4 (0D8132Ah)  
    CTest Test1;
00D84A92  lea         ecx,[Test1]  
00D84A95  call        CTest::CTest (0D813E8h)  
    Test1.m_nInt1 = 1;
00D84A9A  mov         dword ptr [Test1],1  
    Test1.m_nInt2 = 2;
00D84AA1  mov         dword ptr [ebp-2Ch],2  
    Test1.PrintfCTestNum(Test1);
00D84AA8  sub         esp,28h  
00D84AAB  mov         ecx,0Ah  
00D84AB0  lea         esi,[Test1]  
00D84AB3  mov         edi,esp  
00D84AB5  rep movs    dword ptr es:[edi],dword ptr [esi]    这里直接复制对象的数据
00D84AB7  lea         ecx,[Test1]  
00D84ABA  call        CTest::PrintfCTestNum (0D813E3h)  
    return 0;
00D84ABF  xor         eax,eax  

 对象作为函数参数的释放问题

  在把对象作为函数参数传入的时候,其实就相当于创建一个对象赋值为传入对象的值,也就是复制一个传入的对象,这个时候就需要用到了类中的拷贝构造函数,如果没有拷贝构造函数就是默认的类中的拷贝构造函数,就相当于自动进行一个浅拷贝,弄一个和传入对象一模一样的对象,甚至于首地址也是,这个时候如果函数调用完毕会把传入的变量销毁,就会把原来的对象销毁掉。

解决办法: 

   1  为了不让传入的参数被销毁掉,所以需要自己写拷贝构造函数,有了拷贝构造函数,就相当于开辟了一个新对象来把传入的对象的数值完全赋值给新对象,但是并不是对应的一个空间,所以对这个怎么处理都没关系。

   2  利用指针和引入来作为参数处理,这样的话也就根本不需要用到复制一个副本来处理了,就调用指针来通过指针处理传入的的对象的各种数据,而且只需要分配一个临时变量等于指针的值就好了,当函数调用完毕后会自动删除的是临时变量的值,影响不了原来的对象。  

对象作为返回值

  通常基本的数据类型,双精度(double)类型除外的都是用eax作为返回值直接处理,但是对象是自定义的所有不能用eax来直接处理。

  以对象作为返回值和以对象作为函数参数非常类似,做参数时是会在栈空间开辟一个地址来处理,作为返回值的时候,会在堆开辟一个空间来存放对象,然后把这个临时栈空间的首地址作为返回值。但是同样的是返返回值的首地址也是用eax来保存。

int main()
{
00AB1950  push        ebp  
00AB1951  mov         ebp,esp  
00AB1953  sub         esp,15Ch  
00AB1959  push        ebx  
00AB195A  push        esi  
00AB195B  push        edi  
00AB195C  lea         edi,[ebp-15Ch]  
00AB1962  mov         ecx,57h  
00AB1967  mov         eax,0CCCCCCCCh  
00AB196C  rep stos    dword ptr es:[edi]  
00AB196E  mov         ecx,offset _36AAE6AE_源@cpp (0ABC029h)  
00AB1973  call        @__CheckForDebuggerJustMyCode@4 (0AB132Ah)  
    CReturn objA;
    objA = GetCReturn();
00AB1978  lea         eax,[ebp-158h]  ;传入预留的栈空间的首地址
00AB197E  push        eax  
00AB197F  call        GetCReturn (0AB13EDh)  ;调用GetCReturn函数
00AB1984  add         esp,4            ;平衡栈空间
00AB1987  mov         ecx,0Bh          ;设置循环次数
00AB198C  mov         esi,eax           ;调用对象作为返回值后,会把开辟的对象的空间的首地址给eax
00AB198E  lea         edi,[ebp-124h]      ;
00AB1994  rep movs    dword ptr es:[edi],dword ptr [esi]  
00AB1996  mov         ecx,0Bh  
00AB199B  lea         esi,[ebp-124h]  
00AB19A1  lea         edi,[objA]  
00AB19A4  rep movs    dword ptr es:[edi],dword ptr [esi]  



CReturn GetCReturn()
{
00AB4F00 push ebp
00AB4F01 mov ebp,esp
00AB4F03 sub esp,100h
00AB4F09 push ebx
00AB4F0A push esi
00AB4F0B push edi
00AB4F0C lea edi,[ebp-100h]
00AB4F12 mov ecx,40h
00AB4F17 mov eax,0CCCCCCCCh
00AB4F1C rep stos dword ptr es:[edi] ;初始化栈
00AB4F1E mov ecx,offset _36AAE6AE_源@cpp (0ABC029h)
00AB4F23 call @__CheckForDebuggerJustMyCode@4 (0AB132Ah) ;预防栈溢出
CReturn RetObj;
RetObj.m_nNumber = 0;
00AB4F28 mov dword ptr [RetObj],0 ;给对象的首地址的第一个元素赋值
for (int i = 0; i < 10; i++)
00AB4F2F mov dword ptr [ebp-3Ch],0
00AB4F36 jmp GetCReturn+41h (0AB4F41h)
00AB4F38 mov eax,dword ptr [ebp-3Ch]
00AB4F3B add eax,1
00AB4F3E mov dword ptr [ebp-3Ch],eax
00AB4F41 cmp dword ptr [ebp-3Ch],0Ah
00AB4F45 jge GetCReturn+56h (0AB4F56h)
{
RetObj.m_nArry[i] = i + 1;
00AB4F47 mov eax,dword ptr [ebp-3Ch]
00AB4F4A add eax,1
00AB4F4D mov ecx,dword ptr [ebp-3Ch]
00AB4F50 mov dword ptr [ebp+ecx*4-2Ch],eax
}
00AB4F54 jmp GetCReturn+38h (0AB4F38h)
return RetObj;
00AB4F56 mov ecx,0Bh ;循环赋值的次数
00AB4F5B lea esi,[RetObj] ;把需要首地址给esi
00AB4F5E mov edi,dword ptr [ebp+8]   ;把开辟的空间的首地址给edi
00AB4F61 rep movs dword ptr es:[edi],dword ptr [esi]   ;把栈中的数据给堆中开辟的数据 
00AB4F63 mov eax,dword ptr [ebp+8]   ;返回首地址给eax
}

 

  函数在调用GetCReturn之间,先在main中将申请的返回对象的首地址作为参数入栈,然后再在函数调用完后进行了数据赋值,将GetCReturn中定义的局部对象的数据复制到这个返回对象的空间中,再讲这个返回的对象复制给目标对象obja,从而达成了返回对象的目的。这里的返回对象也可以算是一个临时对象,但是作为临时对象而目标对象是临时对象的复制,那么在释放的时候肯定会有问题,存在 出错的风险,这与对象作为参数意义,如果析构函数有资源释放的操作,就可以造成同一资源多次释放。  

  和对象做函数参数的区别:  这里不能用对象指针或者引用来处理,因为对临时对象进行指针或者引用会导致返回已经被释放的局部变量的地址

#include<iostream>
using namespace std;

class CReturn
{
public:
    int m_nNumber;
    int m_nArry[10];
};
CReturn GetCReturn()
{
    CReturn RetObj;
    RetObj.m_nNumber = 0;
    for (int i = 0; i < 10; i++)
    {
        RetObj.m_nArry[i] = i + 1;
    }
    return RetObj;
}
int main()
{
    CReturn objA;
    objA = GetCReturn();
    printf("%d %d %d \n", objA.m_nNumber, objA.m_nArry[0], objA.m_nArry[9]);
    return 0;

}