- 浏览: 361317 次
- 性别:
- 来自: 成都
文章分类
最新评论
-
tuspark:
关于javadoc这里讲解的更全面:《javadoc设置》。
Eclipse中生成javadoc【Z】 -
yuexiang1007:
帮我解决了问题,谢谢!!!
java.math.BigInteger使用心得总结 -
netwelfare:
个人感觉,文章对HashMap的遍历分析的有点浅,不如这里的介 ...
HashMap遍历的两种方式【Z】 -
memoryisking:
关于java.math.BigInteger讲解在这里可以看到 ...
java.math.BigInteger使用心得总结 -
巴尾的兔兔帅:
divide应该是除吧?不是减。dividepublic Bi ...
java.math.BigInteger使用心得总结
转载-->C++ 对象的内存布局 陈皓
前言
07年
12月,我写了一篇《
C++虚函数表解析
》
的文章,引起了大家的兴趣。有很多朋友对我的文章留了言,有鼓励我的,有批评我的,还有很多问问题的。我在这里一并对大家的留言表示感谢。这也是我为什么
再写一篇续言的原因。因为,在上一篇文章中,我用了的示例都是非常简单的,主要是为了说明一些机理上的问题,也是为了图一些表达上方便和简单。不想,这篇
文章成为了打开
C++对象模型内存布局的一个引子,引发了大家对
C++对象的更深层次的讨论。当然,我之前的文章还有很多方面没有涉及,从我个人感觉下来,在谈论虚函数表里,至少有以下这些内容没有涉及:
1) 有成员变量的情况。
2) 有重复继承的情况。
3) 有虚拟继承的情况。
4) 有钻石型虚拟继承的情况。
这些都是我本篇文章需要向大家说明的东西。所以,这篇文章将会是《 C++虚函数表解析 》的一个续篇,也是一篇高级进阶的文章。我希望大家在读这篇文章之前对 C++有一定的基础和了解,并能先读我的上一篇文章。因为这篇文章的深度可能会比较深,而且会比较杂乱,我希望你在读本篇文章时不会有大脑思维紊乱导致大脑死机的情况。 ;-)
对象的影响因素
简而言之,我们一个类可能会有如下的影响因素:
1) 成员变量
2) 虚函数(产生虚函数表)
3) 单一继承(只继承于一个类)
4) 多重继承(继承多个类)
5) 重复继承(继承的多个父类中其父类有相同的超类)
6) 虚拟继承(使用 virtual方式继承,为了保证继承后父类的内存布局只会存在一份)
上述的东西通常是 C++这门语言在语义方面对对象内部的影响因素,当然,还会有编译器的影响(比如优化),还有字节对齐的影响。在这里我们都不讨论,我们只讨论 C++语言上的影响。
本篇文章着重讨论下述几个情况下的 C++对象的内存布局情况。
1) 单一的一般继承 (带成员变量、虚函数、虚函数覆盖)
2) 单一的虚拟继承 (带成员变量、虚函数、虚函数覆盖)
3) 多重继承 (带成员变量、虚函数、虚函数覆盖)
4) 重复多重继承 (带成员变量、虚函数、虚函数覆盖)
5) 钻石型的虚拟多重继承 (带成员变量、虚函数、虚函数覆盖)
我们的目标就是,让事情越来越复杂。
知识复习
我们简单地复习一下,我们可以通过对象的地址来取得虚函数表的地址,如:
typedef void (*Fun)( void );
Base b;
Fun pFun = NULL;
cout << " 虚函数表地址: " << ( int *)(&b) << endl;
cout << " 虚函数表 — 第一个函数地址: " << ( int *)*( int *)(&b) << endl;
// Invoke the first virtual function
pFun = (Fun)*(( int *)*( int *)(&b));
pFun();
我们同样可以用这种方式来取得整个对象实例的内存布局。因为这些东西在内存中都是连续分布的,我们只需要使用适当的地址偏移量,我们就可以获得整个内存对象的布局。
本篇文章中的例程或内存布局主要使用如下编译器和系统:
1) Windows XP 和 VC++ 2003
2) Cygwin 和 G++ 3.4.4
单一的一般继承
下面,我们假设有如下所示的一个继承关系:
请注意,在这个继承关系中,父类,子类,孙子类都有自己的一个成员变量。而了类覆盖了父类的 f()方法,孙子类覆盖了子类的 g_child()及其超类的 f()。
我们的源程序如下所示:
class Parent {
public :
int iparent;
Parent ():iparent ( 10 ) {}
virtual void f() { cout << " Parent::f()" << endl; }
virtual void g() { cout << " Parent::g()" << endl; }
virtual void h() { cout << " Parent::h()" << endl; }
};
class Child : public Parent {
public :
int ichild;
Child():ichild( 100 ) {}
virtual void f() { cout << "Child::f()" << endl; }
virtual void g_child() { cout << "Child::g_child()" << endl; }
virtual void h_child() { cout << "Child::h_child()" << endl; }
};
class GrandChild : public Child{
public :
int igrandchild;
GrandChild():igrandchild( 1000 ) {}
virtual void f() { cout << "GrandChild::f()" << endl; }
virtual void g_child() { cout << "GrandChild::g_child()" << endl; }
virtual void h_grandchild() { cout << "GrandChild::h_grandchild()" << endl; }
};
我们使用以下程序作为测试程序:(下面程序中,我使用了一个 int** pVtab 来作为遍历对象内存布局的指针,这样,我就可以方便地像使用数组一样来遍历所有的成员包括其虚函数表了,在后面的程序中,我也是用这样的方法的,请不必感到奇怪,)
typedef
void
(*Fun)(
void
);
GrandChild gc;
int ** pVtab = ( int **)&gc;
cout << "[0] GrandChild::_vptr->" << endl;
for ( int i= 0 ; (Fun)pVtab[ 0 ][i]!=NULL; i++){
pFun = (Fun)pVtab[ 0 ][i];
cout << " [" <<i<< "] " ;
pFun();
}
cout << "[1] Parent.iparent = " << ( int )pVtab[ 1 ] << endl;
cout << "[2] Child.ichild = " << ( int )pVtab[ 2 ] << endl;
cout << "[3] GrandChild.igrandchild = " << ( int )pVtab[ 3 ] << endl;
其运行结果如下所示:(在 VC++ 2003和 G++ 3.4.4下)
[0] GrandChild::_vptr-> [0] GrandChild::f() [1] Parent::g() [2] Parent::h() [3] GrandChild::g_child() [4] Child::h1() [5] GrandChild::h_grandchild() [1] Parent.iparent = 10 [2] Child.ichild = 100 [3] GrandChild.igrandchild = 1000 |
使用图片表示如下:
可见以下几个方面:
1) 虚函数表在最前面的位置。
2) 成员变量根据其继承和声明顺序依次放在后面。
3) 在单一的继承中,被 overwrite的虚函数在虚函数表中得到了更新。
多重继承
下面,再让我们来看看多重继承中的情况,假设有下面这样一个类的继承关系。注意:子类只 overwrite了父类的 f()函数,而还有一个是自己的函数(我们这样做的目的是为了用 g1()作为一个标记来标明子类的虚函数表)。而且每个类中都有一个自己的成员变量:
我们的类继承的源代码如下所示:父类的成员初始为 10, 20, 30,子类的为 100
class Base1 {
public :
int ibase1;
Base1():ibase1( 10 ) {}
virtual void f() { cout << "Base1::f()" << endl; }
virtual void g() { cout << "Base1::g()" << endl; }
virtual void h() { cout << "Base1::h()" << endl; }
};
class Base2 {
public :
int ibase2;
Base2():ibase2( 20 ) {}
virtual void f() { cout << "Base2::f()" << endl; }
virtual void g() { cout << "Base2::g()" << endl; }
virtual void h() { cout << "Base2::h()" << endl; }
};
class Base3 {
public :
int ibase3;
Base3():ibase3( 30 ) {}
virtual void f() { cout << "Base3::f()" << endl; }
virtual void g() { cout << "Base3::g()" << endl; }
virtual void h() { cout << "Base3::h()" << endl; }
};
class Derive : public Base1, public Base2, public Base3 {
public :
int iderive;
Derive():iderive( 100 ) {}
virtual void f() { cout << "Derive::f()" << endl; }
virtual void g1() { cout << "Derive::g1()" << endl; }
};
我们通过下面的程序来查看子类实例的内存布局:下面程序中,注意我使用了一个 s变量,其中用到了 sizof(Base)来找下一个类的偏移量。(因为我声明的是 int成员,所以是 4个字节,所以没有对齐问题。关于内存的对齐问题,大家可以自行试验,我在这里就不多说了)
typedef
void
(*Fun)(
void
);
Derive d;
int ** pVtab = ( int **)&d;
cout << "[0] Base1::_vptr->" << endl;
pFun = (Fun)pVtab[ 0 ][ 0 ];
cout << " [0] " ;
pFun();
pFun = (Fun)pVtab[ 0 ][ 1 ];
cout << " [1] " ;pFun();
pFun = (Fun)pVtab[ 0 ][ 2 ];
cout << " [2] " ;pFun();
pFun = (Fun)pVtab[ 0 ][ 3 ];
cout << " [3] " ; pFun();
pFun = (Fun)pVtab[ 0 ][ 4 ];
cout << " [4] " ; cout<<pFun<<endl;
cout << "[1] Base1.ibase1 = " << ( int )pVtab[ 1 ] << endl;
int s = sizeof (Base1)/ 4 ;
cout << "[" << s << "] Base2::_vptr->" <<endl;
pFun = (Fun)pVtab[s][ 0 ];
cout << " [0] " ; pFun();
Fun = (Fun)pVtab[s][ 1 ];
cout << " [1] " ; pFun();
pFun = (Fun)pVtab[s][ 2 ];
cout << " [2] " ; pFun();
pFun = (Fun)pVtab[s][ 3 ];
out << " [3] " ;
cout<<pFun<<endl;
cout << "[" << s+ 1 << "] Base2.ibase2 = " << ( int )pVtab[s+ 1 ] << endl;
s = s +
sizeof
(Base2)/
4
;
cout <<
"["
<sp
发表评论
-
C#设计模式[链接]
2011-01-05 14:19 836http://zhenyulu.cnblogs.com/cat ... -
C/C++预处理、编译、链接过程【Z】
2011-01-05 14:07 1758在Linux下进行C语言编程,必然要采用GNU GCC来编 ... -
c语言输出重定向【Z】
2010-10-03 22:41 2996可以使用重定向操作符将命令输入和输出数据流从默认位置重定向到不 ... -
C++标准库【Z】
2010-09-29 22:42 882C++标准库的内容分为10类: C1.语言支持 C2.输入/ ... -
c语言socket编程指南
2010-09-29 20:39 1262介绍 Socket 编程让你沮丧吗?从man page ... -
kmp算法【Z】
2010-09-24 19:02 779我们这里说的KMP不是拿来放电影的(虽然我很喜欢这个软件), ... -
虚拟继承、虚函数学习总结【Z】
2010-09-24 16:03 1443虚拟继承、虚函数学习总结 ... -
C++对象内存布局测试总结【Z】
2010-09-24 16:02 1097对于普通的C++对象内存布局,简单得不得了,就不做总结了。这里 ... -
C++隐式成员函数2【Z】
2010-09-24 15:37 9301 编译器自动生成的基本函数 C++编译器会在开发人员没有声 ... -
C++隐式成员函数【Z】
2010-09-24 15:27 1259这篇文章讲述的是C++提供的一些由编译器自 ... -
extern详解【Z】
2010-09-24 14:44 7761 基本解释 extern可 ... -
KMP字符串匹配算法【Z】
2010-09-14 22:44 1146最普通的字符串匹配 ... -
C语言变量存储类型
2010-09-14 19:58 1176C语言变量存储类型 auto static extern ... -
C/C++中的void
2010-09-14 19:54 9431.void的含义 (1)voi ... -
排序算法
2010-09-10 11:10 10461、稳定排序和非稳定排序 简单地说就是所有相等的数经过某种排 ... -
C++ Socket连接实现【转载】
2010-08-31 21:27 2914C++ socket程序 下面是一个C++调用windo ... -
虚函数/纯虚函数
2010-08-31 19:17 5241.首先:强调一个概念 定义一个函数 ... -
纯虚函数
2010-08-31 19:07 1006一、定义. 纯虚函数是在基类 中声明的虚函数,它在基类中没 ... -
虚继承
2010-08-31 16:06 780虚拟继承在一般的应用 ... -
C++ 抽象
2010-08-31 10:45 748在C++中,以类、虚函数等为代表的数据抽象功能一直是C++的核 ...
相关推荐
C++对象内存布局[归类].pdf
涉及各种情况下C++对象的sizeof大小,包括单一类对象,继承,重复继承 多继承 单一虚继承 等各种情况下的对象大小。对C++对象内存布局有清楚了解。
介绍C++对象在内存中是怎样分布的,有助于深层学习C++。
安卓逆向学习笔记之ART中的C++对象内存布局及获取art-method和dex-file对象.docx
c++ 标准不规定 c++ 实现的时候的对象的具体的内存布局,除了在某些方面有小的限制以外,c++ 对象在内存里面的布局完全是由编译器自行决定,这里只是讨论 vc++ .net 2003 build 7.1.3091 的实现方式.
详细理解vs2008C++编程内存使用情况!!值得一看...
C++ 对象的内存布局。全面分析C++ 对象的内存布局。
c++对象的内存布局 对c++做了非常通俗 而且经典的分析 如果你想对c++的工作方式有更深入的了解 这是一份非常有帮助的文档
1)有成员变量的情况。 2)有重复继承的情况。 3)有虚拟继承的情况。 4)有钻石型虚拟继承的情况。
C++对象模型在内存中的实现,讲述了类,继承以及虚继承的内存布局;成员变量和成员函数的访问已经访问时的开销情况,包含虚函数的情况,考察构造函数,析构函数,以及特殊的赋值操作符成员函数是如何工作的,数组是...
C++ 对象的内存布局(下)1
C++对象的内存布局[归纳].pdf
讲解 C++ 对象内存布局的非常好的一本书,可以从最底层,最本质的地方认识 C++ 的类,从而掌握面向对象技术的精髓 。
这是一些关于基类含有virtual函数或子类是virtual继承的对象的内存布局。其中有我截的一些图、内存布局图、文字说明,不过能力有限,说的不是很清楚,望谅解
网上看到的一篇博客,对C++对象内存模式讲的详细易懂。
分析c++对象在内存中的布局情况.pdf
主要讲述了c++对象内存布局情况!
-) 对象的影响因素 简而言之,我们一个类可能会有如下的影响因素: 1)成员变量2)虚函数(产生虚函数表)3)单一继承(只继承于一个类)4)多重继承(继承多个类
c++继承中的内存布局 VC++对象模型
详细介绍C++ 对象模型, 对象内存布局