1.3. 包含方法的对象
包含了方法的对象,其大小和内存布局应该是什么样子的呢?下面让我们来看一看。
1.3.1. 不包含虚函数的对象
考察下面的类:
class Simple
{
public :
Simple(char _a, int _i);
private :
char a;
int i;
};
它的大小应该是多少呢?数据占用了8个字节,函数应该占用多少呢?我们来做个测试。这一次让人惊异的是它的大小没发生变化,仍然是8。那么它的内存布局应该是
char a (1 Byte)
补齐占位(3 Byte)
int i (4 Byte)
Simple(char _a, int _i)
Simple对象内存布局
这样的内存布局对不对呢?让我们设计一个程序验证一下。
0001 #include <iostream>
0002 #include <conio.h>
0003 using namespace std;
0004 //----------------------------------------------------------------
0005 class Simple
0006 {
0007 public :
0008 Simple(char _a, int _i);
0009 private :
0010 char a;
0011 int i;
0012 };
0013 //----------------------------------------------------------------
0014 Simple::Simple(char _a, int _i)
0015 {
0016 a = _a;
0017 i = _i;
0018 }
0019 //----------------------------------------------------------------
0020 int main(int argc, char* argv[])
0021 {
0022 cout << "Sizeof(Simple)" << '\t' << sizeof(Simple) << endl;
0023 Simple a('c', 9);
0024 int *ip = (int *)&a;
0025 char *cp = (char *)&a;
0026 cout << "Simple.a" << '\t' << *cp << endl;
0027 cout << "Simple.i" << '\t' << *++ip << endl;
0028 getch();
0029 return 0;
0030 }
0031 //----------------------------------------------------------------
结果符合我们的猜测。
1.3.2. 包含虚函数的对象
考察下面的类:
class Simple
{
public :
Simple(char _a, int _i);
virtual void vPrint(void);
void Print(void);
private :
char a;
int i;
};
这一次他的大小是多少呢?按照上面的经验,函数不占据对象的存储空间,那么他的大小应该是8。对不对呢?再来检验一次。结果怎么变成“12”了呢?慢着,让我们想一想,虚函数?它意味着什么呢?虚函数意味着多态性,也就是运行期才决定调用哪个函数。那么这时如何实现的呢?答案是virtual function table,也就是通常所说的虚函数表。通过virtual function table的间接引用,我们就可以在运行期间决定调用哪一个函数。那么如何能够获得virtual function table?这就需要用到一个叫做vtbl的指针,有这个指针指向virtual function table的开始地址,那么对于虚函数的调用就变成了通过virtual function table的引用,调用函数指针。当然,这对于执行期来将是有效率的代价的。包含虚函数的对象模型如下:
void vPrint(void)
char a (1 Byte)
补齐占位(3 Byte)
int i (4 Byte)
vtbl (4 Byte)
数据布局 虚函数表
Simple(char _a, int _i)
void Print(void)
Simple对象内存布局
让我们对此作一个验证
0001 #include <iostream>
0002 #include <conio.h>
0003 using namespace std;
0004 //----------------------------------------------------------------
0005 class Simple
0006 {
0007 public :
0008 Simple(char _a, int _i);
0009 virtual void vPrint(void);
0010 void Print(void);
0011 private :
0012 char a;
0013 int i;
0014 };
0015 //----------------------------------------------------------------
0016 Simple::Simple(char _a, int _i)
0017 {
0018 a = _a;
0019 i = _i;
0020 }
0021 //----------------------------------------------------------------
0022 void Simple::Print(void)
0023 {
0024 cout << "Simple.a" << a << '\t' << "Simple.i" << i << endl;
0025 }
0026 //----------------------------------------------------------------
0027 void Simple::vPrint(void)
0028 {
0029 cout << "Simple.a" << a << '\t' << "Simple.i" << i << endl;
0030 }
0031 //----------------------------------------------------------------
0032 int main(int argc, char* argv[])
0033 {
0034 cout << "Sizeof(Simple)" << '\t' << sizeof(Simple) << endl;
0035 Simple a('c', 9);
0036 int *ip = (int *)&a;
0037 char *cp = (char *)&a;
0038 cout << "Simple.a" << '\t' << *cp << endl;
0039 cout << "Simple.i" << '\t' << *++ip << endl;
0040 cout << "Simple.vtbl" << '\t' << *++ip << endl;
0041 getch();
0042 return 0;
0043 }
0044 //----------------------------------------------------------------
程序的输出为:
Sizeof(Simple) 12
Simple.a c
Simple.i 9
Simple.vtbl 4269384
非常符合我们的推测。以上是gcc的编译结果,在C++ Builder上,代码和结果略有不同
cout << "Sizeof(Simple)" << '\t' << sizeof(Simple) << endl;
Simple a('c', 9);
int *ip = (int *)&a;
char *cp = (char *)&a;
cout << "Simple.vtbl" << '\t' << *ip << endl;
cout << "Simple.a" << '\t' << *(cp+4) << endl;
cout << "Simple.i" << '\t' << *(ip+2) << endl;
程序的输出为:
Sizeof(Simple) 12
Simple.vtbl 4207548
Simple.a c
Simple.i 9
不同处在于vtbl在对象内存布局的开始处还是结尾处。