② 继承类对象:box和sphere(分别为盒子和球体)的存储格局分别如下面两张表所示。
box
offset |
description |
---|---|
+0x0 |
int color |
+0x4 |
int width |
+0x8 |
int height |
+0xC |
int depth |
sphere
offset |
description |
---|---|
+0x0 |
int color |
+0x4 |
int radius |
下面分析一下函数主体main()。
指令清单51.11 MSVC 2008带参数/Ob0的优化
PUBLIC _main
_TEXT SEGMENT
_s$ = -24 ; size = 8
_b$ = -16 ; size = 16
_main PROC
sub esp, 24
push 30
push 20
push 10
push 1
lea ecx, DWORD PTR _b$[esp+40]
call ??0box@@QAE@HHHH@Z ; box::box
push 40
push 2
lea ecx, DWORD PTR _s$[esp+32]
call ??0sphere@@QAE@HH@Z ; sphere::sphere
lea ecx, DWORD PTR _b$[esp+24]
call ?print_color@object@@QAEXXZ ; object::print_color
lea ecx, DWORD PTR _s$[esp+24]
call ?print_color@object@@QAEXXZ ; object::print_color
lea ecx, DWORD PTR _b$[esp+24]
call ?dump@box@@QAEXXZ ; box::dump
lea ecx, DWORD PTR _s$[esp+24]
call ?dump@sphere@@QAEXXZ ; sphere::dump
xor eax, eax
add esp, 24
ret 0
_main ENDP
继承类必须在其基(父)类字段的后面加入自己的字段,因此基类和继承类的类成员函数可以共存。
当程序调用类成员对象object::print_color()时,指向对象box和sphere的指针是通过this指针传递的。由于在所有继承类和基类中color字段的偏移量固定为0(offset+0x0),所有类对象的类成员函数object::print_color都可以正常运行。
因此,无论是基类还是继承类调用object::print_color(),只要该方法所引用的字段的相对地址固定不变,那么该方法就可以正常运行。
假如基于box类创建一个继承类,那么编译器就会在变量depth的后面追加您所添加新的变量,以确保基类box的各字段的相对地址在其继承类中固定不变。
因此,当父类为box类的各继承类在调用各自的方法box::dump()时,它们都能检索到color、width、height以及depths字段的正确地址。因为各字段的相对地址不会发生变化。
GCC生成的指令代码与MSVC生成的代码几乎相同。唯一的区别是:GCC不会使用ECX寄存器传递this指针,它会以函数的第一个参数的传递方式传递this指针。
封装(encapsulation)的作用是:把既定的数据和方法限定为类的私有信息,使得其他调用方只能访问类所定义的公共方法和公共数据、不能直接访问被封装起来的私有对象。
在指令层面,到底有没有划分私有对象和公开对象的界限呢?
其实完全没有。
我们来看看这一个简单的例子:
#include <stdio.h>
class box
{
private:
int color, width, height, depth;
public:
box(int color, int width, int height, int depth)
{
this->color=color;
this->width=width;
this->height=height;
this->depth=depth;
};
void dump()
{
printf ("this is box. color=%d, width=%d, height=%d, depth=%d\n", color, width,height, depth);
};
};
我们启用MSVC 2008的优化选项/Ox和/Ob0编译上述程序,再查看类函数box::dump()的代码。
?dump@box@@QAEXXZ PROC ; box::dump, COMDAT
; _this$ = ecx
mov eax, DWORD PTR [ecx+12]
mov edx, DWORD PTR [ecx+8]
push eax
mov eax, DWORD PTR [ecx+4]
mov ecx, DWORD PTR [ecx]
push edx
push eax
push ecx
; 'this is box. color=%d, width=%d, height=%d, depth=%d', 0aH, 00H
push OFFSET ??_C@_0DG@NCNGAADL@this?5is?5box?4?5color?$DN?$CFd?0?5width?$DN?$CFd?0@
call _printf
add esp, 20
ret 0
?dump@box@@QAEXXZ ENDP ; box::dump
下面这个表格显示了类的变量在内存中的偏移量的分布情况。
offset |
description |
---|---|
+0x0 |
int color |
+0x4 |
int width |
+0x8 |
int height |
+0xC |
int depth |
所有字段都是无法被其他函数直接访问的私有变量。但是,既然我们知道了这个对象的内存存储格局,能不能写出一个修改这些变量的程序呢?
为此,我们可以构造一个名称为hack_oop_encapsulation()的函数。如果不做调整的话,直接访问既定字段的源程序大致会是:
void hack_oop_encapsulation(class box * o)
{
o->width=1; // that code cant be compiled':
// "error C2248: 'box::width' : cannot access private member declared in class 'box'"
};
当然,上述代码不可能被成功编译出来。然而,只要把box的数据类型强制转换为整型数组的话,我们就可以通过编译并且直接修改相应字段。
void hack_oop_encapsulation(class box * o)
{
unsigned int *ptr_to_object=reinterpret_cast<unsigned int*>(o);
ptr_to_object[1]=123;
};
上述函数的功能十分简单:它将输入数据视为整型数组,然后将数组的第二个元素、一个整型int值修改为123 。
?hack_oop_encapsulation@@YAXPAVbox@@@Z PROC ; hack_oop_encapsulation
mov eax, DWORD PTR _o$[esp-4]
mov DWORD PTR [eax+4], 123
ret 0
?hack_oop_encapsulation@@YAXPAVbox@@@Z ENDP ; hack_oop_encapsulation
接下来,我们验证一下它的功能。
int main()
{
box b(1, 10, 20, 30);
b.dump();
hack_oop_encapsulation(&b);
b.dump();
return 0;
};
运行的结果为:
this is box. color=1, width=10, height=20, depth=30
this is box. color=1, width=123, height=20, depth=30
我们可以看到,封装只能够在编译阶段保护类的私有对象。虽然C++编译器禁止外部代码直接访问那些被明确屏蔽的内部对象,但是通过适当的hack技术,我们确实能够突破编译器的限制策略。
多重继承,指的是一个类可以同时继承多个父类的字段和方法。
我们还是写个简单的例子:
#include <stdio.h>
class box
{
public:
int width, height, depth;
box() { };
box(int width, int height, int depth)
{
this->width=width;
this->height=height;
this->depth=depth;
};
void dump()
{
printf ("this is box. width=%d, height=%d, depth=%d\n", width, height, depth);
};
int get_volume()
{
return width * height * depth;
};
};
class solid_object
{
public:
int density;
solid_object() { };
solid_object(int density)
{
this->density=density;
};
int get_density()
{
return density;
};
void dump()
{
printf ("this is solid_object. density=%d\n", density);
};
};
class solid_box: box, solid_object
{
public:
solid_box (int width, int height, int depth, int density)
{
this->width=width;
this->height=height;
this->depth=depth;
this->density=density;
};
void dump()
{
printf ("this is solid_box. width=%d, height=%d, depth=%d, density=%d\n", width,↙
↘ height, depth, density);
};
int get_weight() { return get_volume() * get_density(); };
};
int main()
{
box b(10, 20, 30);
solid_object so(100);
solid_box sb(10, 20, 30, 3);
b.dump();
so.dump();
sb.dump();
printf ("%d\n", sb.get_weight());
return 0;
};
在启用其优化选项(/Ox和/Ob0)后,我们使用MSVC编译上述程序,重点观察box::dump()、solid_object::dump()以及solid_box::dump()这3个类成员函数。
指令清单51.12 带/Ob0参数的MSVC 2008优化程序
?dump@box@@QAEXXZ PROC ; box::dump, COMDAT
; _this$ = ecx
mov eax, DWORD PTR [ecx+8]
mov edx, DWORD PTR [ecx+4]
push eax
mov eax, DWORD PTR [ecx]
push edx
push eax
; 'this is box. width=%d, height=%d, depth=%d', 0aH, 00H
push OFFSET ??_C@_0CM@DIKPHDFI@this?5is?5box?4?5width?$DN?$CFd?0?5height?$DN?$CFd@
call _printf
add esp, 16
ret 0
?dump@box@@QAEXXZ ENDP ; box::dump
指令清单51.13 带/Ob0参数的MSVC 2008优化程序
?dump@solid_object@@QAEXXZ PROC ; solid_object::dump, COMDAT
; _this$ = ecx
mov eax, DWORD PTR [ecx]
push eax
; 'this is solid_object. density=%d', 0aH
push OFFSET ??_C@_0CC@KICFJINL@this?5is?5solid_object?4?5density?$DN?$CFd@
call _printf
add esp, 8
ret 0
?dump@solid_object@@QAEXXZ ENDP ; solid_object::dump
指令清单51.14 带/Ob0参数的MSVC 2008优化程序
?dump@solid_box@@QAEXXZ PROC ; solid_box::dump, COMDAT
; _this$ = ecx
mov eax, DWORD PTR [ecx+12]
mov edx, DWORD PTR [ecx+8]
push eax
mov eax, DWORD PTR [ecx+4]
mov ecx, DWORD PTR [ecx]
push edx
push eax
push ecx
; 'this is solid_box. width=%d, height=%d, depth=%d, density=%d', 0aH
push OFFSET ??_C@_0DO@HNCNIHNN@this?5is?5solid_box?4?5width?$DN?$CFd?0?5hei@
call _printf
add esp, 20
ret 0
?dump@solid_box@@QAEXXZ ENDP ; solid_box::dump
上述3个类对象的内存分布如下:
① 类box。如下表所示。
offset |
description |
---|---|
+0x0 |
width |
+0x4 |
height |
+0x8 |
depth |
② 类solid_object。如下表所示。
offset |
description |
---|---|
+0x0 |
density |
③ 类solid_box,可以看成是以上两个类的联合体。如下表所示。
offset |
description |
---|---|
+0x0 |
width |
+0x4 |
height |
+0x8 |
depth |
+0xC |
density |