C++对象模型_Class Obj作为函数参数

开发环境
  • VC6.0
  • 编辑器 Cmd Markdown
  1. 关于C/C++中基本类型(如:int,int*等)作为函数参数时,是通过将该变量的值压栈来进行参数传递;本文通过C++反汇编代码分析了当对象作为函数参数时(该形参非引用或指针),参数如何传递以及此时栈帧的结构。

  2. 对象作为函数参数时,参数传递过程(如:函数的声明为:void show(class Object obj);该函数的调用的为show(arg);其中实参arg的类型为class Object):1,在栈顶上为obj对象分配内存空间,然后将对象arg的首地址压栈;2,调用拷贝构造函数(此为C++中三种调用拷贝构造函数情况之一),将arg的数据成员拷贝至obj;3,执行show()函数体(此时,ebp+8即为obj的首地址)。

  3. 具体分析过程,见代码注释。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
//C++源码。
//VC6.0
#include<iostream>
#includestdio>
using namespace std;
class CBase{
int i;
public:
CBase(int i=0)
{
this->i=i;
}
CBase(CBase& rhs) //拷贝构造函数。
{
i=rhs.i;
printf("拷贝构造函数=%d\n",i);
}
void show(CBase B1, CBase B2) //对象作为形参。
{
printf("show=%d,%d\n",B1.i,B2.i);
}
};
int main()
{
CBase Base;
CBase Basex(1);
CBase Basexx(2);
Base.show(Basex,Basexx);
// printf("this is an end!\n");
}
---------------------------------------------------------------------------------
//main函数对应的汇编代码。
int main()
24: {
0040D4A0 push ebp
0040D4A1 mov ebp,esp
0040D4A3 sub esp,54h
0040D4A6 push ebx
0040D4A7 push esi
0040D4A8 push edi
0040D4A9 lea edi,[ebp-54h]
0040D4AC mov ecx,15h
0040D4B1 mov eax,0CCCCCCCCh
0040D4B6 rep stos dword ptr [edi]
25: CBase Base;
0040D4B8 push 0
0040D4BA lea ecx,[ebp-4] //ecx保存的是对象Base的this指针。
0040D4BD call @ILT+0(CBase::CBase) (00401005)//调用构造函数
26: CBase Basex(1);
0040D4C2 push 1
0040D4C4 lea ecx,[ebp-8] //ecx保存的是对象Basex的this指针。
0040D4C7 call @ILT+0(CBase::CBase) (00401005)//调用构造函数
27: CBase Basexx(2);
0040D4CC push 2
0040D4CE lea ecx,[ebp-0Ch] //ecx保存的是对象Basexx的this指针。
0040D4D1 call @ILT+0(CBase::CBase) (00401005)//调用构造函数
28: Base.show(Basex,Basexx);
0040D4D6 push ecx //等价于esp=esp-4;
0040D4D7 mov ecx,esp //ecx保存的是show函数的形参B2的this指针。
0040D4D9 lea eax,[ebp-0Ch]
0040D4DC push eax //对Basexx的this(即Basexx对象的首地址)指针压栈。
0040D4DD call @ILT+10(CBase::CBase) (0040100f)//调用拷贝构造函数
0040D4E2 push ecx //等价于esp=esp-4;
0040D4E3 mov ecx,esp //ecx保存的是show函数的形参B1的this指针。
0040D4E5 lea edx,[ebp-8]
0040D4E8 push edx //对Basex的this(即Basex对象的首地址)指针压栈。
0040D4E9 call @ILT+10(CBase::CBase) (0040100f)//调用拷贝构造函数
0040D4EE lea ecx,[ebp-4] //ecx保存的是Base对象的this指针。
0040D4F1 call @ILT+20(CBase::show) (00401019)//调用show函数。
29: printf("this is an end!\n");
0040D4F6 push offset string "this is an end!\n" (00422fa4)
0040D4FB call printf (0040d7c0)
0040D500 add esp,4
30: }
0040D503 pop edi
0040D504 pop esi
0040D505 pop ebx
0040D506 add esp,54h
0040D509 cmp ebp,esp
0040D50B call __chkesp (004010b0)
0040D510 mov esp,ebp
0040D512 pop ebp
0040D513 ret
//show函数对应的汇编代码。
16: void show(CBase B1,CBase B2)
17: {
0040D550 push ebp
0040D551 mov ebp,esp
0040D553 sub esp,44h
0040D556 push ebx
0040D557 push esi
0040D558 push edi
0040D559 push ecx
0040D55A lea edi,[ebp-44h]
0040D55D mov ecx,11h
0040D562 mov eax,0CCCCCCCCh
0040D567 rep stos dword ptr [edi]
0040D569 pop ecx
0040D56A mov dword ptr [ebp-4],ecx
18: printf("show=%d,%d\n",B1.i,B2.i);
0040D56D mov eax,dword ptr [ebp+0Ch] //ebp+0Ch对应的是B2的首地址。
0040D570 push eax
0040D571 mov ecx,dword ptr [ebp+8] //ebp+8h对应的是B1的首地址。
0040D574 push ecx
0040D575 push offset string "show=%d,%d\n" (00422fcc)
0040D57A call printf (0040d7c0)
0040D57F add esp,0Ch
19: }
0040D582 pop edi
0040D583 pop esi
0040D584 pop ebx
0040D585 add esp,44h
0040D588 cmp ebp,esp
0040D58A call __chkesp (004010b0)
0040D58F mov esp,ebp
0040D591 pop ebp
0040D592 ret 8
//拷贝构造函数对应的汇编代码(注释略)。
11: CBase(CBase& rhs)
0040D840 push ebp
0040D841 mov ebp,esp
0040D843 sub esp,44h
0040D846 push ebx
0040D847 push esi
0040D848 push edi
0040D849 push ecx
0040D84A lea edi,[ebp-44h]
0040D84D mov ecx,11h
0040D852 mov eax,0CCCCCCCCh
0040D857 rep stos dword ptr [edi]
0040D859 pop ecx
0040D85A mov dword ptr [ebp-4],ecx
12: {
13: i=rhs.i;
0040D85D mov eax,dword ptr [ebp-4]
0040D860 mov ecx,dword ptr [ebp+8]
0040D863 mov edx,dword ptr [ecx]
0040D865 mov dword ptr [eax],edx
14: printf("拷贝构造函数=%d\n",i);
0040D867 mov eax,dword ptr [ebp-4]
0040D86A mov ecx,dword ptr [eax]
0040D86C push ecx
0040D86D push offset string "\xbf\xbd\xb1\xb4\xb9\xb9\xd4\xec\xba\xaf\xca\xfd=%d\n" (00422fb8)
0040D872 call printf (0040d7c0)
0040D877 add esp,8
15: }
0040D87A mov eax,dword ptr [ebp-4]
0040D87D pop edi
0040D87E pop esi
0040D87F pop ebx
0040D880 add esp,44h
0040D883 cmp ebp,esp
0040D885 call __chkesp (004010b0)
0040D88A mov esp,ebp
0040D88C pop ebp
0040D88D ret 4
  1. 当执行show函数作用域内代码时,栈结构图示如下:

stack

C++对象模型_operator delete异常分析

开发环境
  • VC6.0
  • 编辑器 Cmd Markdown
  1. C++中delete表达式执行的操作是:1,调用析构函数;2,释放对象内存(operator delete(…))。

  2. 如果父类的析构函数没有声明为virtual函数,且子类中至少存在一个virtual函数,此时将子类的对象地址赋值给父类指针。当对父类的指针执行delete操作时,会调用父类析构函数,然后在释放内存时(即delete表达式执行的操作的2,释放对象内存)出现崩溃。然而如果子类中不存在一个virtual函数时,执行上面同样的操作就不会出现崩溃。

  • 原因分析如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//已知本示例 父类的析构函数应声明为virtual函数。但是由于本程序不需要析构函数执行特殊的操作,所以delete父类指针pB同样可以释放内存,然而引起出乎意料的内//存释放异常。因此对本程序进行如下分析
#include<iostream>
#include stdio>
using namespace std;
class Base
{
public:
~Base() {printf("\nBase::destructor.");}
};
class Derived: public Base
{
virtual void show()
{
cout<<"show"<
}
public:
~Derived(){printf("\nDerived::destructor.");}
};
int main()
{
Base* pB=NULL;
Derived *pD= new Derived;
pB=pD;//此时pB得到的是(unsigned char*)pD+4的地址,所以执行operator delete时会发生崩溃(因为此时把vptr的内存当成了待释放的内存块的大小)。
/*unsigned char *pC=(unsigned char*)pB;
pC=pC-4;
delete pC;//这样就可以释放new Derived分配的内存,而不会发生崩溃。
*/
delete pB;
}
  • 其main函数对应的汇编代码如下(VC6.0):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    int main()
    23: {
    00401060 push ebp
    00401061 mov ebp,esp
    00401063 push 0FFh
    00401065 push offset __ehhandler$_main (00416a0b)
    0040106A mov eax,fs:[00000000]
    00401070 push eax
    00401071 mov dword ptr fs:[0],esp
    00401078 sub esp,64h
    0040107B push ebx
    0040107C push esi
    0040107D push edi
    0040107E lea edi,[ebp-70h]
    00401081 mov ecx,19h
    00401086 mov eax,0CCCCCCCCh
    0040108B rep stos dword ptr [edi]
    24:Base* pB=NULL;
    0040108D mov dword ptr [ebp-10h],0
    25: Derived *pD= new Derived;
    00401094 push 4
    00401096 call operator new (00403780)
    0040109B add esp,4
    0040109E mov dword ptr [ebp-1Ch],eax //eax保存的是operator new(...)函数分配的内存的首地址。
    004010A1 mov dword ptr [ebp-4],0
    004010A8 cmp dword ptr [ebp-1Ch],0 //判断operator new(...)函数分配内存是否成功。
    004010AC je main+5Bh (004010bb)
    004010AE mov ecx,dword ptr [ebp-1Ch] //调用Derived::Derived()函数,ecx保存的是内存指针。
    004010B1 call @ILT+35(Derived::Derived) (00401028)
    004010B6 mov dword ptr [ebp-28h],eax
    004010B9 jmp main+62h (004010c2)
    004010BB mov dword ptr [ebp-28h],0
    004010C2 mov eax,dword ptr [ebp-28h]
    004010C5 mov dword ptr [ebp-18h],eax
    004010C8 mov dword ptr [ebp-4],0FFFFFFFFh
    004010CF mov ecx,dword ptr [ebp-18h]
    004010D2 mov dword ptr [ebp-14h],ecx
    26: pB=pD;
    004010D5 cmp dword ptr [ebp-14h],0
    004010D9 je main+86h (004010e6)
    004010DB mov edx,dword ptr [ebp-14h]
    004010DE add edx,4
    004010E1 mov dword ptr [ebp-2Ch],edx
    004010E4 jmp main+8Dh (004010ed)
    004010E6 mov dword ptr [ebp-2Ch],0
    004010ED mov eax,dword ptr [ebp-2Ch]
    004010F0 mov dword ptr [ebp-10h],eax //pB=(unsinged char*)(pD)+4
    27:
    28: delete pB;
    004010F3 mov ecx,dword ptr [ebp-10h]
    004010F6 mov dword ptr [ebp-24h],ecx
    004010F9 mov edx,dword ptr [ebp-24h]
    004010FC mov dword ptr [ebp-20h],edx
    004010FF cmp dword ptr [ebp-20h],0
    00401103 je main+0B4h (00401114)
    00401105 push 1
    00401107 mov ecx,dword ptr [ebp-20h]
    0040110A call @ILT+20(Base::`scalar deleting destructor
  • 图示如下

cxxmodel

奇怪的死循环

1
2
3
4
5
6
7
8
9
10
11
12
#include<stdio.h>
int main()
{
int i;
int a[10];
for(i=0;i<=10;++i)
{
a[i]=0;
printf("%d\n",a[i]);
}
return 0;
}

该程序对应的汇编代码见如下代码:

大小端法互换

1.以int32类型为例:

  • 方法1:
1
2
3
4
5
6
7
8
9
10
11
12
13
void swapInt(int *rhs)
{
unsigned char *p=rhs;
unsigned char temp;
temp=p[0];
p[0]=[1];
p[1]=temp;
temp=p[1];
p[1]=[2];
p[2]=temp;
return ;
}
  • 方法2:
1
2
3
4
5
void swapInt(int *rhs)
{
*rhs=(((*rhs)&0xff000000)>>24) | (((*rhs)&0x00ff0000)>>8) \
| (((*rhs)&0x000000ff)<<24) | (((*rhs)&0x0000ff00)<<8);
}

2.检测机器字节序:大端法or小端法

  • 方法1:
1
2
3
4
5
6
int checkEndian()//检查主机字节顺序是否是大端法,如果是,返回1,否则返回0.
{
int i=0x12345678;
unsigned char *p=&i;
return (0x12==p[0]);
}
  • 方法2:
1
2
3
4
5
6
7
8
9
int checkEndian()
{
union{
unsigned int i;
unsigned char s[4];
}c;
c.i=0x12345678;
return (0x12==c.s[0]);
}

MFC thunk技术模拟

//参考http://www.cnblogs.com/satng/archive/2010/12/30/2138833.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
#include<iostream>
using namespace std;
//thunk技术模拟
typedef void (*fun)(void *,int i);
class CFun;//类声明。
#pragma pack(push)
#pragma pack(1)
typedef struct Thunk{
unsigned char call;
int offset;
fun pf;//函数指针。
unsigned char code[5];
CFun *ths;//this指针。
unsigned char jmp;
unsigned char ecx;
}Thunk;
#pragma pack(pop)
#define OFF(s,m) ((unsigned int)&((s*)0)->m)//求结构体的偏移量,s为结构体的类型,m为结构体的数据成员。
class CFun{
public:
CFun()
{
createThunk();
}
~CFun()
{
delete thunk;
}
public:
void createThunk()
{
Thunk* tk=new Thunk;
//call des
tk->call=0xE8;//call
tk->offset=OFF(Thunk,code[0])-OFF(Thunk,pf);//des
tk->pf=CFun::funx;//函数地址。
//pop ecx
//等价于:
//mov ecx,[esp]
//sub esp,4
tk->code[0]=0x59;//pop ecx
//mov [esp+4],this
tk->code[1]=0xc7;//mov
tk->code[2]=0x44;//dword ptr
//4[esp]
tk->code[3]=0x24;//[esp]
tk->code[4]=0x04;//+4
tk->ths=this;//修改栈,设置this指针。
//jmp [ecx]
tk->jmp=0xFF;//jmp
tk->ecx=0x21;//[ecx]
thunk=(fun)tk;
return ;
}
static void funx(void *pFun,int i)
{
CFun *pf=(CFun*)pFun;
pf->print(i);
}
void print(int i )
{
cout<<"Recevie="<<i<<endl;
}
fun GetThunk()
{
return thunk;
}
private:
fun thunk;
};
int main()
{
CFun cf;
fun pf=cf.GetThunk();
pf("Hello",123);
return 0;
}

24 bits解析为有符号整数

最近遇到了24bits来解析为有符号整数的问题,提供如下两个解决思路:

方法1:

24bits即从高位到低位为:buf[0],buf[1],buf[2] 三个bytes;通过将最高字节buf[0]符号扩展为两个字节(假设从高位到低位为ps[1],ps[0]),然后将ps[1],ps[0],buf[1],buf[2]拼接为int变量的四个字节;见下图;
pic1