- 浏览: 369199 次
- 性别:
- 来自: 成都
文章分类
最新评论
-
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 872http://zhenyulu.cnblogs.com/cat ... -
C/C++预处理、编译、链接过程【Z】
2011-01-05 14:07 1802在Linux下进行C语言编程,必然要采用GNU GCC来编 ... -
c语言输出重定向【Z】
2010-10-03 22:41 3030可以使用重定向操作符将命令输入和输出数据流从默认位置重定向到不 ... -
C++标准库【Z】
2010-09-29 22:42 923C++标准库的内容分为10类: C1.语言支持 C2.输入/ ... -
c语言socket编程指南
2010-09-29 20:39 1297介绍 Socket 编程让你沮丧吗?从man page ... -
kmp算法【Z】
2010-09-24 19:02 818我们这里说的KMP不是拿来放电影的(虽然我很喜欢这个软件), ... -
虚拟继承、虚函数学习总结【Z】
2010-09-24 16:03 1486虚拟继承、虚函数学习总结 ... -
C++对象内存布局测试总结【Z】
2010-09-24 16:02 1134对于普通的C++对象内存布局,简单得不得了,就不做总结了。这里 ... -
C++隐式成员函数2【Z】
2010-09-24 15:37 9711 编译器自动生成的基本函数 C++编译器会在开发人员没有声 ... -
C++隐式成员函数【Z】
2010-09-24 15:27 1302这篇文章讲述的是C++提供的一些由编译器自 ... -
extern详解【Z】
2010-09-24 14:44 8131 基本解释 extern可 ... -
KMP字符串匹配算法【Z】
2010-09-14 22:44 1186最普通的字符串匹配 ... -
C语言变量存储类型
2010-09-14 19:58 1216C语言变量存储类型 auto static extern ... -
C/C++中的void
2010-09-14 19:54 9741.void的含义 (1)voi ... -
排序算法
2010-09-10 11:10 10891、稳定排序和非稳定排序 简单地说就是所有相等的数经过某种排 ... -
C++ Socket连接实现【转载】
2010-08-31 21:27 2950C++ socket程序 下面是一个C++调用windo ... -
虚函数/纯虚函数
2010-08-31 19:17 5481.首先:强调一个概念 定义一个函数 ... -
纯虚函数
2010-08-31 19:07 1043一、定义. 纯虚函数是在基类 中声明的虚函数,它在基类中没 ... -
虚继承
2010-08-31 16:06 815虚拟继承在一般的应用 ... -
C++ 抽象
2010-08-31 10:45 791在C++中,以类、虚函数等为代表的数据抽象功能一直是C++的核 ...
相关推荐
C++对象内存布局 C++对象内存布局是指在C++编程语言中对象在内存中的存储结构和布局方式。了解C++对象的内存布局对于理解C++编程语言的对象模型和内存管理机制非常重要。 从内存布局的角度来看,C++对象可以分为三...
### C++对象内存布局 #### 1. 最简单的类 在C++中,理解对象的内存布局对于深入学习语言特性非常关键。通过分析一个简单的类`CTest`,我们可以更好地了解对象是如何在内存中分配和组织的。 ##### 1.1.1 赋值语句...
#### 一、基类对象内存布局 在C++中,对象的内存布局受到多种因素的影响,例如成员变量的类型、是否有虚函数等。接下来我们将详细探讨不同情况下的对象内存布局。 ##### 1. **空类** 空类是指没有任何成员变量的...
在Visual Studio 2008 (VS...通过深入理解VS2008下的C++对象内存布局,开发者可以更好地优化代码,避免内存相关的错误,并提升程序性能。对于大型项目,了解这些细节至关重要,因为它们直接影响程序的稳定性和效率。
在C++编程中,对象内存布局是一个非常关键的概念,它涉及到如何在内存中存储对象以及如何进行指针转换。在不同的编译器和平台上,内存布局可能会有所不同,但有一些通用的原则和约定。本文主要探讨了VC++ .NET 2003 ...
安卓逆向学习笔记之ART中的C++对象内存布局及获取art-method和dex-file对象.docx
本文将详细探讨不同类型的继承情况对C++对象内存布局的影响。 首先,我们需要理解对象内存布局的基本元素。每个C++对象通常包含三个部分:数据成员(即类中定义的变量)、虚函数表指针(如果类有虚函数)以及可能...
本文将详细分析C++对象内存布局的各种情况,涵盖单一继承、虚拟继承、多重继承、重复继承以及钻石型虚拟多重继承。 首先,我们回顾一下获取对象内存布局的基本方法。在C++中,对象的地址可以用来获取虚函数表的地址...
了解C++对象内存布局的额外问题,比如C++封装带来的布局成本有多大?对于一个由空类组成的继承层次中,每个类对象的大小是多少?这些问题的答案取决于编译器的具体实现和内存对齐的规则。C++编译器通常需要为每个类...
### C++继承中的内存布局详解 #### 1. 类如何布局? 在C++中,类的内存布局受到多个因素的影响,包括成员变量的类型、成员函数的存在与否、继承类型(如单继承、多重继承或虚继承)等。下面分别探讨不同情况下的类...
在C++编程中,类的对象内存布局是一个关键的议题,特别是涉及到多态性、继承和虚函数时。这里我们将深入探讨这些概念,并结合你提供的"类的继承"这个主题进行详细解析。 首先,让我们理解一个类的对象在内存中的...
C++ 对象的内存布局(下) C++ 对象的内存布局是指在内存中如何存储和组织对象的成员变量和虚函数表的信息。了解 C++ 对象的内存布局对理解和优化 C++ 程序非常重要。 在 C++ 中,对象的内存布局主要由编译器决定...
总的来说,理解C++对象的内存布局对于优化代码、调试和设计高效的数据结构至关重要。不同的继承策略和编译器优化会带来不同的内存开销和性能影响,因此在编写代码时需要谨慎选择。在实际编程中,应该遵循“最小权限...
C++对象模型在内存中的实现,讲述了类,继承以及虚继承的内存布局;成员变量和成员函数的访问已经访问时的开销情况,包含虚函数的情况,考察构造函数,析构函数,以及特殊的赋值操作符成员函数是如何工作的,数组是...
#### 内存布局与管理 在C++中,对象在内存中的布局主要包括: **1. 对象头** 用于存储对象的元数据,如RTTI(运行时类型信息)或虚函数表指针。 **2. 数据成员** 对象的实际数据,按声明顺序排列。 **3. ...
从给定的文件信息来看,文章深入探讨了C/C++中的内存编程,特别是关于C++对象内存布局的细节,这是程序设计中一个重要的概念。文章不仅涵盖了基础的内存布局,如空类、普通类和静态成员的处理,还进一步探讨了更复杂...
讲解 C++ 对象内存布局的非常好的一本书,可以从最底层,最本质的地方认识 C++ 的类,从而掌握面向对象技术的精髓 。
C++对象模型是C++编程的基础,它涉及到内存布局、虚函数、多态性、继承和封装等多个方面。首先,书中会详细讲解C++对象在内存中的表示方式,包括对象头、成员变量和成员函数的存储位置。理解这一部分对于优化内存...