C++类对应的内存结构
http://blog.csdn.net/guogangj/archive/2007/05/25/1625199.aspx,
本文使用的表示方法和VC6的Memory视图一致,即:左上表示低位。
提示2:下文提到的“类大小”严格上来说是该类经过实例化的对象的大小。当然了,光研究长度的话,两者差别不大,因为:CClassA objA,sizeof(CClassA)和sizeof(objA)得到的结果都是一样的。
一、真空类
class CNull
{
};
长度:1
内存结构:
??
评注:长度其实为0,这个字节作为内容没有意义,可能每次都不一样。
二、空类
class CNull2
{
public:
CNull2(){printf("Construct\n");}
~CNull2(){printf("Desctruct\n");}
void Foo(){printf("Foo\n");}
};
长度:1
内存结构:
??
评注:同真空类差不多,内部的成员函数并不会影响类大小。
三、简单类
class COneMember
{
public:
COneMember(int iValue = 0){m_iOne = iValue;};
private:
int m_iOne;
};
长度:4
内存结构:
00 00 00 00 //m_iOne
评注:成员数据才影响类大小。
四、简单继承
class CTwoMember:public COneMember
{
private:
int m_iTwo;
};
长度:8
内存结构:
00 00 00 00 //m_iOne
CC CC CC CC //m_iTwo
评注:子类成员接在父类成员之后。
五、再继承
class CThreemember:public CTwoMember
{
public:
CThreemember(int iValue=10) {m_iThree = iValue;};
private:
int m_iThree;
};
长度:12
内存结构:
00 00 00 00 //m_iOne
CC CC CC CC //m_iTwo
0A 00 00 00 //m_iThree
评注:孙类成员接在子类之后,再再继承就依此类推了。
六、多重继承
class ClassA
{
public:
ClassA(int iValue=1){m_iA = iValue;};
private:
int m_iA;
};
class ClassB
{
public:
ClassB(int iValue=2){m_iB = iValue;};
private:
int m_iB;
};
class ClassC
{
public:
ClassC(int iValue=3){m_iC = iValue;};
private:
int m_iC;
};
class CComplex :public ClassA, public ClassB, public ClassC
{
public:
CComplex(int iValue=4){m_iComplex = iValue;};
private:
int m_iComplex;
};
长度:16
内存结构:
01 00 00 00 //A
02 00 00 00 //B
03 00 00 00 //C
04 00 00 00 //Complex
评注:也是父类成员先出现在前边,我想这都足够好理解。
七、复杂一些的继承
不写代码了,怕读者看了眼花,改画图。
长度:32
内存结构:
01 00 00 00 //A
02 00 00 00 //B
03 00 00 00 //C
04 00 00 00 //Complex
00 00 00 00 //OneMember
CC CC CC CC //TwoMember
0A 00 00 00 //ThreeMember
05 00 00 00 //VeryComplex
评注:还是把自己的成员放在最后。
只要没涉及到“虚”(Virtual),我想没什么难点,不巧的是“虚”正是我们要研究的内容。
八、趁热打铁,看“虚继承”
class CTwoMember:virtual public COneMember
{
private:
int m_iTwo;
};
长度:12
内存结构:
E8 2F 42 00 //指针,指向一个关于偏移量的数组,且称之虚基类偏移量表指针
CC CC CC CC // m_iTwo
00 00 00 00 // m_iOne(虚基类数据成员)
评注:virtual让长度增加了4,其实是多了一个指针,关于这个指针,确实有些复杂,别的文章有具体分析,这里就不岔开具体讲了,可认为它指向一个关于虚基类偏移量的数组,偏移量是关于虚基类数据成员的偏移量。
九、“闭合”虚继承,看看效果
长度:24
内存结构:
14 30 42 00 //ClassB的虚基类偏移量表指针
02 00 00 00 //m_iB
C4 2F 42 00 //ClassC的虚基类偏移量表指针
03 00 00 00 //m_iC
04 00 00 00 //m_iComplex
01 00 00 00 //m_iA
评注:和预料中的一样,虚基类的成员m_iA只出现了一次,而且是在最后边。当然了,更复杂的情况要比这个难分析得多,但虚继承不是我们研究的重点,我们只需要知道:虚继承利用一个“虚基类偏移量表指针”来使得虚基类即使被重复继承也只会出现一次。
十、看一下关于static成员
class CStaticNull
{
public:
CStaticNull(){printf("Construct\n");}
~CStaticNull(){printf("Desctruct\n");}
static void Foo(){printf("Foo\n");}
static int m_iValue;
};
长度:1
内存结构:(同CNull2)
评注:可见static成员不会占用类的大小,static成员的存在区域为静态区,可认为它们是“全局”的,只是不提供全局的访问而已,这跟C的static其实没什么区别。
十一、带一个虚函数的空类
class CVirtualNull
{
public:
CVirtualNull(){printf("Construct\n");}
~CVirtualNull(){printf("Desctruct\n");}
virtual void Foo(){printf("Foo\n");}
};
长度:4
内存结构:
00 31 42 00 //指向虚函数表的指针(虚函数表后面简称“虚表”)
00423100:(虚表)
41 10 40 00 //指向虚函数Foo的指针
00401041:
E9 78 02 00 00 E9 C3 03 … //函数Foo的内容(看不懂)
评注:带虚函数的类长度就增加了4,这个4其实就是个指针,指向虚函数表的指针,上面这个例子中虚表只有一个函数指针,值就是“0x00401041”,指向的这个地址就是函数的入口了。
十二、继承带虚函数的类
class CVirtualDerived : public CVirtualNull
{
public:
CVirtualDerived(){m_iVD=0xFF;};
~CVirtualDerived(){};
private:
int m_iVD;
};
长度:8
内存结构:
3C 50 42 00 //虚表指针
FF 00 00 00 //m_iVD
0042503C:(虚表)
23 10 40 00 //指向虚函数Foo的指针,如果这时候创建一个CVirtualNull对象,会发现它的虚表的内容跟这个一样
评注:由于父类带了虚函数,子类就算没有显式声明虚函数,虚表还是存在的,虚表存放的位置跟父类不同,但内容是同的,也就是对父类虚表的复制。
十三、子类有新的虚函数
class CVirtualDerived: public CVirtualNull
{
public:
CVirtualDerived(){m_iVD=0xFF;};
~CVirtualDerived(){};
virtual void Foo2(){printf("Foo2\n");};
private:
int m_iVD;
};
长度:8
内存结构:
24 61 42 00 //虚表指针
FF 00 00 00 //m_iVD
00426124:(虚表)
23 10 40 00
50 10 40 00
评注:虚表还是只有一张,不会因为增加了新的虚函数而多出另一张来,新的虚函数的指针将添加在复制了的虚表的后面。
十四、当纯虚函数(pure function)出现时
class CPureVirtual
{
virtual void Foo() = 0;
};
class CDerivePV : public CPureVirtual
{
void Foo(){printf("vd: Foo\n");};
};
长度:4(CPureVirtual),4(CDerivePV)
内存结构:
CPureVirtual:
(不可实例化)
CDerivePV:
28 50 42 00 //虚表指针
00425028:(虚表)
5A 10 40 00 //指向Foo的函数指针
评注:带纯虚函数的类不可实例化,因此列不出其“内存结构”,由其派生类实现纯虚函数。我们可以看到CDerivePV虽然没有virtual声明,但由于其父类带virtual,所以还是继承了虚表,如果CDerivePV有子类,还是这个道理。
十五、虚函数类的多重继承
前面提到:(子类的虚表)不会因为增加了新的虚函数而多出另一张来,但如果有多重继承的话情况就不是这样了。下例中你将看到两张虚表。
大小:24
内存结构
F8 50 42 00 //虚表指针
01 00 00 00 //m_iA
02 00 00 00 //m_iB
E8 50 42 00 //虚表指针
03 00 00 00 //m_iC
04 00 00 00 //m_iComplex
004250F8:(虚表)
5A 10 40 00 //FooA
55 10 40 00 //FooB
64 10 40 00 //FooComplex
004250E8:(虚表)
5F 10 40 00 //FooC
评注:子类的虚函数接在第一个基类的虚函数表的后面,所以B接在A后面,Complex接在B后面。基类依次出现,子类成员接在最后面,所以m_iComplex位于最后面。
本来还想看看更复杂些的情况,甚至包括虚继承和虚函数同时出现的多重多层继承情况,但确实有些复杂了,自己还有些找不到规律,所以准备之后再补充。
C++ 中的 std::string 类 C++ 在其定义中有一种将字符序列表示为 class 对象的方法。这个类叫做 std::string。String 类将字符存储为具有允许访问单字节字符的功能的字节序列。
Android C++系列:JNI调用 Java 类的构造方法和父类的方法 Android JNI开发时经常遇到C/C++层访问Java层对象的,比如C/C++层创建一个String返回,或者访问Java层提供的MediaCodec等,此时我们就需要通过 JNI 来调用 Java 一个类的构造方法来创建这个 Java 类。
C++ 编程std::string类 td::string是C++标准库中的一个类,它用于表示字符串,在C++中是一个非常常用的数据类型。std::string可以保存任意长度的字符串,并且支持各种字符串操作,包括连接、查找、替换等等。
相关文章
- 【C/C++】内存管理详解
- C++:多线程内存管理的思考
- C++常用算法(一):遍历【for_each:遍历容器元素】【transform:将指定容器区间元素搬运到另一容器中】
- C++-map:map判断key是否存在【myMap.find(item)!=myMap.end()】
- C/C++:指针、引用【指针:指针是一个变量,只不过这个变量中存储的是一个地址,指向内存中的一个单元】【引用:引用和原变量是同一个东西,只不过是原变量的一个别名】
- matlab mex中C++内存全局共享和持久化
- C++哈希-使用/模拟/封装
- Lua 跟 C++ 的交互
- C/C++ 内存对齐
- c/c++内存调试
- [C++]3-1 得分(Score ACM-ICPC Seoul 2005,UVa1585)
- 【c++】类管理指针成员
- Python/C++ OpenCV手势运动方向检测
- 【C++】C++核心编程部分-内存分区模型-引用-函数提高-类与对象-文件操作
- C/C++中编译程序的内存结构分布
- 【ThinkingInC++】64、重载new和delete,来模仿内存的分配
- 全面整理的C++面试题
- 2021-09-04 《C++ Primer》学习记录:第4、5、6章
- 数据结构 - 表插入排序 具体解释 及 代码(C++)
- Effective C++ (二) 构造、析构和赋值运算
- VC++ CHtmlView与Javascript交互
- 【C++】C++的工具库
- C/C++程序中内存被非法改写的一个检测方法
- Visual Leak Detector 2.2.3 Visual C++内存检测工具
- 用c++builder读取一个一行有多行变量的文件
- [C++基础] 数组、指针、内存篇
- C++:简单理解 在模拟STL容器时内存泄漏问题
- C++:通过对象自动管理内存,往智能指针靠近,再实现部分运算符重载