C 对象布局及多态实现探索之内存布局

2008-02-23 05:27:04来源:互联网 阅读 ()

新老客户大回馈,云服务器低至5折

 前言

  本文通过观察对象的内存布局,跟踪函数调用的汇编代码。分析了C 对象内存的布局情况,虚函数的执行方式,连同虚继承,等等。

  写这篇文章源于我在论坛上看到的一个贴子。有人问VC使用了哪种方式来实现虚继承。当时我写了一点代码想验证一下,结果发现情况比我想象的要复杂。所以我就干脆认真把相关的问题都过了一遍,并记录成本文。

  我对于C 对象模型的知识主要来自于Lippman的书《Inside the C Object Model》,中译版为候捷翻的《深度探索C 对象模型》,中英版我都看过,但是我还是推荐中译版,因为中译版的确翻得不错,而且候捷加入了很多的图,并修正了原版中的一些错误。

  我所使用的编译器是VC7.1,文中的代码我都在VC7.1上验证通过。假如在其他的编译器下运行需要作相应的调整,即使是VC7.0和VC6也是如此。不同编译器产生的汇编代码也不相同,假如您在不同编译器上编译文中的代码生成出的汇编代码和我所列出的不同,也不足为奇。假如您想在其他的编译器上验证这些代码请自行做相应的改变。

  另外我发现VC7.1在实现虚继承时所用的方法和Lippman在书中提到的微软所用的方法不同,但是那时还没有VC7.1。有趣的是,Lippman在写那本书时,是在迪斯尼工作,应该是做和三维影片的渲染软件相关的事。而现在他已到了微软,相信应该是主导VC7.1编译器的设计工作。

  在后文中能够看到列出的很多汇编代码,有些明显效率很低。这可能是因为我没有打开编译器的优化开关。打开优化开关,配置不同的优化选项后,编译器可能产生出高效得多的汇编代码。有兴趣的朋友能够自行试试,并和文中列出的汇编代码做一下比较。

  为了便于分析和观察对象的内存布局,我把代码生成时的结构成员对齐选项配置为1字节,默认为8字节。假如您在自己的工程下编译文中的代码,请做同样的配置。因为我写了一些函数打印对象中的布局信息,假如对象选项不是1字节,运行这些代码会出现指针异常错误。

  普通类对象的内存布局

  首先我们从普通类对象的内存布局开始。C000为一个空类,定义如下:

struct C000
{};

  运行如下代码打印他的大小及对象中的内容。

PRINT_SIZE_DETAIL(C000)

  结果为:

The size of C000 is 1
The detail of C000 is cc

  能够看到他的大小为1字节,这是个占位符。我们能够看到他的值是0xcc。在debug模式下,这表示是由编译器插入的调试代码所初始化的内存。在release模式下可能是个随机值,我测试时值为0x00。

  定义两个类,C010和C011如下:

struct C010
{
 C010() : c_(0x01) {}
 void foo() { c_ = 0x02; }
 char c_;
};
struct C011
{
 C011() : c1_(0x02), c2_(0x03) {}
 char c1_;
 char c2_;
};

  运行如下代码打印他们的大小及对象中的内容。

PRINT_SIZE_DETAIL(C010)
PRINT_SIZE_DETAIL(C012)

  结果为:

The size of C010 is 1
The detail of C010 is 01
The size of C011 is 2
The detail of C011 is 02 03

  我们从对象的内存输出中能够看到,他们的值就是我们在构造函数中赋的值,C010为0x01,C011为0x0203。大小分别为1、2。

  定义C012类。

struct C012
{
 static int sfoo() { return 1; }
 int foo() { return 1; }
 char c_;
 static int i_;
};
int C012::i_ = 1;

  在这个类中我们加入了一个静态数据成员,一个普通成员函数和一个静态成员函数。

  运行如下代码打印他的大小及对象中的内容。

PRINT_SIZE_DETAIL(C012)

  结果为:

The size of C012 is 1
The detail of C012 is cc

  能够看到他的大小还是1字节,值为0xcc是因为我们没有初始化他,原因前面说过了。

  从上面的结果我们能够映证,普通成员函数,静态成员函数,及静态成员变量皆不会在类的对象中有所表示,成员函数和对象的关联由编译器在编译时处理,正如我们会在后面看到的那样,编译器会在编译时决议出正确的普通成员函数地址,并将对象的地址以this指针的方式,做为第一个参数传递给普通成员函数,以此来进行关联。静态成员函数类似于全局函数,不和具体的对象关联。静态成员变量也相同。静态成员函数和静态成员变量和普通的全局函数及全局变量不同之处在于他们多了一层名字限定。

  普通继承类对象的内存布局

  下面看看普通继承类对象的内存布局。

  定义一个空类C014从C011继承,再定义C015也是个空类从C010和C011继承。

struct C010
{

标签:

版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有

上一篇: C 箴言:考虑可选的虚拟函数的替代方法

下一篇: C 箴言:绝不重定义继承的非虚拟函数