动态网站制作指南
[  QQ表情  ]
[ 投票调查 ]
[ 企业邮箱 ]
[ 网站空间 ]
网络编程 | 站长之家 | 网页制作 | 图形图象 | 操作系统 | 冲浪宝典 | 软件教学 | 网络办公 | 邮件系统 | 网络安全 | 认证考试 | 系统进程
ASP源码 | .Net源码 | PHP源码 | JSP源码 | JAVA源码 | CGI源码 | VB源码 | C++源码 | Delphi源码 | PB源码 | VF源码 | 汇编 | 服务器
电脑书籍下载:程序设计书籍 | 数据库教程书籍 | 平面与多媒体书籍 | 网络通讯书籍 | 系统管理书籍 | 网络安全书籍 | 认证考试书籍
Firefox | IE | Maxthon | 迅雷 | 电驴 | BitComet | FlashGet | QQ | QQ空间 | Vista | 输入法 | Ghost | Word | Excel | wps | Powerpoint
asp | .net | php | jsp | Sql | c# | Ajax | xml | Dreamweaver | FrontPages | Javascript | css | photoshop | fireworks | Flash | Cad | Discuz!
当前位置 > 网站建设学院 > 网络编程 > C/C++教程
Tag:注入,存储过程,分页,安全,优化,xmlhttp,fso,jmail,application,session,防盗链,stream,无组件,组件,md5,乱码,缓存,加密,验证码,算法,cookies,ubb,正则表达式,水印,索引,日志,压缩,base64,url重写,上传,控件,Web.config,JDBC,函数,内存,PDF,迁移,结构,破解,编译,配置,进程
网络编程:ASP教程,ASP.NET教程,PHP教程,JSP教程,C#教程,数据库,XML教程,Ajax,Java,Perl,Shell,VB教程,Delphi,C/C++教程,软件工程,J2EE/J2ME,移动开发
文章搜索服务
邮件订阅
输入你的邮件地址,
你将不会错过任何关于:
[ C/C++教程 ]的信息



本月文章推荐
.在CB中响应消息及自定义消息.
.C语言初学者入门讲座 第十一讲 指.
.用VC++制作一个简单的局域网消息.
.俄罗斯方块大全.
.看我不动也会张的大嘴巴!.
..
.使用多线程实现数据实时采集.
.用BCB3.0制作桌面动画.
.经典c程序100例==71--80.
.C++箴言:接口继承和实现继承.
.用C++制作自己的游戏修改器(上).
.C++远程关机API的学习过程.
.谈函数指针(全局/类成员函数)和函.
.简单飘雪程序.
.C++程序中导出Word文档简易方法.
.取得汉字的笔画的源码(2).
.深入解析C++中的三个修饰符.
.C语言初学者入门讲座 第四讲 运算.
.C++批评系列:继承的本质.
.C语言库函数(G类字母).

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

文章类别:C/C++教程 | 发表日期:2008-3-8 |


前言

  本文通过观察对象的内存布局,跟踪函数调用的汇编代码。分析了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
{
 C010() : c_(0x01) {}
 void foo() { c_ = 0x02; }
 char c_;
};
struct C011
{
 C011() : c1_(0x02), c2_(0x03) {}
 char c1_;
 char c2_;
};
struct C014 : private C011
{};
struct C015 : public C010, private C011
{};
  运行如下代码打印它们的大小及对象中的内容。

PRINT_SIZE_DETAIL(C014)
PRINT_SIZE_DETAIL(C015)
  结果为:

The size of C014 is 2
The detail of C014 is 02 03
The size of C015 is 3
The detail of C015 is 01 02 03
  C014的大小为2字节,也就是C011的大小,对象的内存值也是在C011的构造函数中初始化的两个值0x0203。C015的大小为3字节,也就是C010和C011的大小之和,对象的内存值为0x010203。

  这里我们可以发现父类的成员变量悉数被子类继续,并且于继续方式(公有或私有)无关,如C015是私有继续自C011。继续方式只影响数据成员的“能见度”。子类对象中属于从父类继续的成员变量由父类的构造函数初始化。通常会调用默认构造函数,除非子类在它的构造函数初始化列表中显式调用父类的非默认构造函数。假如没有指定,而父类又没有缺省构造函数,则会产生编译错误。

  我们可以再加一层继续来验证一下。定义类C016,从C015继续,并有自己的4字节int成员变量。

struct C016 : C015
{
 C016() : i_(1) {}
 int i_;
};
  运行如下代码打印它的大小及对象中的内容。

PRINT_SIZE_DETAIL(C016)
  结果为:

The size of C016 is 7
The detail of C016 is 01 02 03 01 00 00 00
  它的大小为7字节,也就是C015的大小(也即是C010和C011的大小和)加上自身的4字节int变量之和。同样对象的内存输出也验证了这一点,前三个字节为从父类继续的,后4个字节为自身的int变量,值为1。

  因此关于普通继续,子类的对象布局为父类中的数据成员加上子类中的数据成员,多层继续时(如C016),顶层类在前,多重继续时则最左父类在前。

上一篇:C++程序设计从零开始之语句 人气:168
下一篇:C++箴言:如何访问模板化基类中的名字 人气:127
点击此处浏览全部C/C++的内容 Dreamweaver插件下载 常用网页广告代码全集
  最新网站源码 最新软件下载
2008-5-20 站长俱乐部新闻发布系统 v5.19
2008-5-20 DotNetTextBox网页编辑器 v3.4.7
2008-5-20 phpMyFAQ v2.5.0 多国语言版
2008-5-20 DreamArticle 文章管理系统 v2.0
2008-5-20 Drupal v7.xdev Build080518
2008-5-20 逐迹内容管理系统AspxNuke v2.0.
2008-5-20 ajax奥运留言本 v1.0
2008-5-20 QQ空间博客全自动挂机互踩好友
2008-5-20 网人采集 v1.2.0
2008-5-7 Windows XP SP3 官方英文版
2008-5-7 Windows XP SP3 官方香港中文版
2008-5-7 Windows XP SP3 官方繁体中文版
2008-5-7 Windows XP SP3 官方简体中文版
2008-4-30 Multiple Unzip Wizard 1.02
2008-4-30 Multiple Unrar Wizard 1.0.0
2008-4-30 WinZip Install/Try/Uninstall a
2008-4-30 ZIP压缩文件修复器WzipFix 2.0
2008-4-30 Pentazip 6.01 Build 189 For Wi
  发表评论
姓 名: 验证码: [ 全部贴吧 ] [ 浏览评论 ]
内 容:
[ 汉字翻译拼音 ] [ 广告代码 ] [ 符号对照表 ] [ 进制转换 ] [ 经典小工具 ] [ 个税计算 ] [ 汉字简繁转换 ] [ 普通单位换算 ] [ 公制单位换算 ]
[ 生辰老黄历 ] [ 国内电话区号 ] [ 国家代码与域名缩写 ] [ 文字加密解密 ] [ 健康查询 ] [ 万年历 ] [ 手机号码查询 ] [ ip搜索 ] [ Google PR查询 ]
业务联系 | 广告刊登 | 频道合作 | 投稿荐稿 | 联系方式 | 加入收藏 | RSS订阅
Copyright © 2000-2008 www.knowsky.com All rights reserved | 网络实名:动态网站制作指南 | 沪ICP备05001343号