两年前写的,欢迎大家吐槽!
转载请注明出处。
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: }