本文主要主要研究C++中数组,结构以及类的反汇编。
1.数组
数组在内存中式一块连续的区域。比如当声明char ch[100]的时候, 我们知道栈是向下增长的, 所以我们开辟地址空间的时候起地址就为[esp(esp会赋值给ebp) – 100, esp].我们可以看下如下的列子:
1 char buf[ 100 ];
2 for ( int i = 0 ; i < 100 ; i ++ )
3 buf[i] = i;
我们看下他的反汇编代码:
1 char buf[ 100 ];
2 for ( int i = 0 ; i < 100 ; i ++ )
3 00BD13A8 mov dword ptr [i], 0
4 00BD13AF jmp wmain + 3Ah (0BD13BAh)
5 00BD13B1 mov eax,dword ptr [i]
6 00BD13B4 add eax, 1
7 00BD13B7 mov dword ptr [i],eax
8 00BD13BA cmp dword ptr [i],64h
9 00BD13BE jge wmain + 4Ch (0BD13CCh) // 到这里是一个典型的for循环标志了
10 buf[i] = i;
11 00BD13C0 mov eax,dword ptr [i] // 把当前i的值赋予eax,这个eax是用来计算内存位置的
12 00BD13C3 mov cl, byte ptr [i] // 因为是char型,所以复制给cl, 这个是给内存赋值的
13 00BD13C6 mov byte ptr [ebp + eax - 6Ch],cl // 计算当前buf[i]在栈中的位置,并赋值
14 00BD13CA jmp wmain + 31h (0BD13B1h)
从上面我们就可以知道buf[0] = [ebp-100]; buf[1] = [ebp -99]……
2.结构体
我们声明这样一个结构体
1 struct myStruct
2 {
3 int a;
4 char b;
5 float c;
6 };
然后调用之:
1 int _tmain( int argc, _TCHAR * argv[])
2 {
3 int n = sizeof (myStruct);
4 n = sizeof ( float );
5 myStruct test;
6 test.a = 0 ;
7 test.b = ' a ' ;
8 test.c = 1.0f ;
9 return 0 ;
10 }
我们看下其内存是如何分布的:
1 int n = sizeof (myStruct);
2 00FB13A8 mov dword ptr [ebp - 0Ch],0Ch
3 n = sizeof ( float );
4 00FB13AF mov dword ptr [ebp - 0Ch], 4
5 myStruct test;
6 test.a = 0 ;
7 00FB13B6 mov dword ptr [ebp - 20h], 0
8 test.b = ' a ' ;
9 00FB13BD mov byte ptr [ebp - 1Ch],61h
10 test.c = 1.0f ;
11 00FB13C1 fld1
12 00FB13C3 fstp dword ptr [ebp - 18h]
我们可以看出这个结构体的大小为OCH(12), float的大小为4, int为4, char为1, 考虑到内存对齐, 大小就得为4的整数倍, 所以为12。
test.a 在内存中的位置为[ebp-20h];
test.b 在内存中的位置为[ebp-2Ch];
test.C 在内存中的位置为[ebp-28h];
3. 类
C++中的类与结构体没有本质上的区别。都是在某一内存地址上开辟地址空间,存放和操作成员变量。我们可以看下如下的测试代码:
1 class myClass
2 {
3 private :
4 int m_a;
5 char m_b;
6 float m_c;
7 public :
8 myClass(){}
9 ~ myClass(){}
10 void SetA( int a)
11 {
12 m_a = a;
13 }
14 virtual void SetB( char b)
15 {
16 m_b = b;
17 }
18 virtual void SetC( float c)
19 {
20 m_c = c;
21 }
22 };
看下其反汇编的实现:
1 int n = sizeof (myClass);
2 0030144D mov dword ptr [ebp - 14h],10h
3 myClass test;
4 00301454 lea ecx,[ebp - 2Ch]
5 00301457 call myClass::myClass (301028h)
6 0030145C mov dword ptr [ebp - 4 ], 0
7 test.SetA( 10 );
8 00301463 push 0Ah
9 00301465 lea ecx,[ebp - 2Ch]
10 00301468 call myClass::SetA (301014h)
11 test.SetB( ' a ' );
12 0030146D push 61h
13 0030146F lea ecx,[ebp - 2Ch]
14 00301472 call myClass::SetB (3011EFh)
15 test.SetC( 1.0f );
16 00301477 push ecx
17 00301478 fld1
18 0030147A fstp dword ptr [esp]
19 0030147D lea ecx,[ebp - 2Ch]
20 00301480 call myClass::SetC (301104h)
21 return 0 ;
22 00301485 mov dword ptr [ebp - 0F8h], 0
23 0030148F mov dword ptr [ebp - 4 ],0FFFFFFFFh
24 00301496 lea ecx,[ebp - 2Ch]
25 00301499 call myClass:: ~ myClass (3010BEh)
26 0030149E mov eax,dword ptr [ebp - 0F8h]
这里先稍微解释下其意思:
# 0030144D 表明这个类的大小为16, 那位什么不是如上结构体的为12呢, 一问类中用到虚函数,所以就有一个指向虚函数列表的指针,其大小为4。
# 00301457 变量调用了该类的构造函数
# 00301468 调用了SetA
# 00301472 调用了SetB
# 00301499 调用了析构函数
我们分别来看下着四个函数内部的实现。
(1) 构造函数
1 00301550 mov dword ptr [ebp - 8 ],ecx
2 00301553 mov eax,dword ptr [ this ] // 首先我们把this指针放入eax中
3 00301556 mov dword ptr [eax],offset myClass::`vftable ' (306744h)//然后把虚函数列表的地址放入[eax]中
4 0030155C mov eax,dword ptr [ this ]
如上,我们可以看出此类的构造函数只是简单得把虚函数列表的指针指向[this]的地址。
(2) SetA
这个函数是一个的成员函数,我们看下其是如何实现的
1 003015E3 mov eax,dword ptr [ this ]
2 003015E6 mov ecx,dword ptr [a]
3 003015E9 mov dword ptr [eax + 4 ],ecx
如上,可以看出很简单,把参数a的值服务[this + 4]这块地址,而这块地址保存的是成员变量m_a的值。
(3) SetB
SetB是一个虚函数, 我们看下其实如何实现的:
1 00301633 mov eax,dword ptr [ this ]
2 00301636 mov cl, byte ptr [b]
3 00301639 mov byte ptr [eax + 8 ],cl
如上可以发现,虚函数的内部实现和普通的函数没有区别, 唯一不同的是虚函数的位置是放在一个类的虚函数列表里面的。
(4) 析构函数
最后我们看下此类的析构函数是怎么实现的
1 003015A0 mov dword ptr [ebp - 8 ],ecx
2 003015A3 mov eax,dword ptr [ this ]
3 003015A6 mov dword ptr [eax],offset myClass::`vftable ' (306744h)
因为我们在析构函数中什么事情也没有干,所以此析构函数只是简单的吧虚函数列表的地址放到[this]中。
这里我总结下声明一个类其内存是如何分配的:
# 当写下myStruct test;的时候会在栈上开辟一个内存空间,其地址为[this]
# 如果此类有虚函数,就有有一个指向此类虚函数列表的指针,其地址为[this ]
# m_a 的地址为[this +4]
# m_b 的地址为[this + 8]
# m_c 的地址为[this + 0Ch]