首页 / C++ / C++成员变量内存模型
C++成员变量内存模型
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了C++成员变量内存模型,小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含5151字,纯文字阅读大概需要8分钟。
内容图文
![C++成员变量内存模型](/upload/InfoBanner/zyjiaocheng/979/3a44120d0acb430189c9b3b4064ce85d.jpg)
0X00.不带继承类内存布局
类变量内存中有哪些内容
静态变量:静态变量被放在全局区的静态区中,并不在变量中。
函数(非类成员函数,成员函数):代码区
每一个类变量的内存布局中没有这个类的函数信息,只包含成员,虚函数表指针(vfptr),虚继承表指针(vtptr)(不同编译器对虚继承实现不一致,本篇用微软的cl编译器做实例)。
class A{
public:
void print() {
cout << d << endl;
}
int d;
};
类A的内存布局如下:
只有这个成员变量,并没有定义的函数信息。
成员的内存地址标准
一个类中的成员变量是如何布局的?
现在我们有一段代码,代码的如下。
class A{
public:
int a;
char a1;
char a2;
char a3;
};
在C++的标准中规定后出现的成员变量应该在内存的更高位地址(这边注意没有规定连续),所以A中的成员变量应该从低地址->高地址顺序为:a->a1->a2->a3。下面这张图是通过vs编译器查看编译后的内存结构,但是只能说明是按一定顺序排列的,我们可以打印出地址查看是否后出现的元素在地址。
通过该代码直接打印出类A中元素的内存地址
A a;
cout << "a.a = "<< (int)(&a.a) << endl;
cout << "a.a1 = " << (int)(&a.a1) << endl;
cout << "a.a2 = " << (int)(&a.a2) << endl;
cout << "a.a3 = " << (int)(&a.a3) << endl;
输出如下
上面输出可以看出,类中的成员变量由出现顺序a->a3, 在内存地址由低到高中的顺序也是a->a3。
这个例子,为了说明成员的地址是根据出现的顺序由低到高这个标准。
什么时候内存不会连续
标准只规定了后面出现的成员变量地址更大(在编译器没有给你做优化的情况下),没有规定连续。
在有内存对齐(详细介绍)的情况(内存对齐是因为某些平台不支持随意的读取内存,只能支持特定位置开始)类成员变量就不会有连续的内存地址。
当类A的定义如下图,这个时候会产生内存对齐。
#include <iostream>
#include "stdio.h"
using namespace std;
class A{
public:
char a1;
int a;//产生内存对齐
char a2;
char a3;
};
int main() {
A a;
//切记输出顺序和变量先后顺序是一样的
cout << "a.a1 = " << (int)(&a.a1) << endl;
cout << "a.a = " << (int)(&a.a) << endl;
cout << "a.a2 = " << (int)(&a.a2) << endl;
cout << "a.a3 = " << (int)(&a.a3) << endl;
}
a1变量虽然是char类型,但是距离a变量也有4个对应字节,编译器会在a1后插入3个字节,a2,a3后有2个字节用于内存对齐。
经过内存对齐后,布局带有“alignment”填充字段。
有虚继承的时候也会导致内存不连续。
0X01带继承的内存布局
继承无虚函数无虚继承
在只有单继承的情况看下类的内存布局,下面是一个类的代码
class A{
int a;
};
class B : public A{
int b;
};
B继承自A,编译后我们看下B的内存布局
可以看出其实就是很简单的把A内存布局拷贝一份到B的起始位置,然后接下去放置B的成员变量。只有的单继承并不会添加别的东西。
多继承的情况也是类似,不会添加任何的东西,只是顺序的把父类的内存布局根据继承的先后顺序拷贝下来(没有虚继承的情况)。
class A{
int a;
};
class B{
int b;
};
class C : public B , public A {
int c;
};
内存布局图如下
只带虚函数的继承
在c++我们经常会声明一个函数为虚函数,那么在有虚函数的时候是什么样子的呢?我们定义一个类A看下编译后的结果
class A{
public:
//规定了一个虚函数
virtual void func() {
}
int a;
};
这个类中除了成员还有一个虚函数,拥有虚函数的类中都有一个指向虚函数表的指针,用于在运行期确定调用的是哪一个函数。
现在我们知道具有虚函数的类内存布局,那么加上继承是什么样呢?其实和没有虚函数一样,子类会把父类的内存空间布局完美的复制一份(在没有虚继承的情况下)。
下面这个类B的内存布局,B继承自A。
class A{
//规定了一个虚函数
virtual void func() {
}
int a;
};
class B : public A{
virtual void func2() {
};
int b;
};
这个是经过编译后看到B的内存布局。
拥有一个虚函数表指针,还有子类的成员和自己的成员。自始至终都只有一个虚函数表指针,保存实际函数地址在于另外一个表中,如下图。
c++中并没有规定虚函数表的实现,不同的编译器对虚函数表也是有各自不同的实现方式。
带虚继承的函数
c++中虚继承主要是用于重复继承相同的父类。解决的问题是:在重复继承父类元素后,一个类中会有重复父类相同的拷贝。
我们有如下的代码,C中重复继承了A类,那么我们C中就会有两个C内存布局的拷贝。
class A{
int a;
};4
class B : public A{
int b;
};
class C : public B, public A{
int c;
};
下面是编译后的C中的内存布局。
很容易看出来在地址为0的时候有a变量,在地址为8的时候有a变量,对使用者来说他只知道有一个a,这就导致了内存的浪费。如果我们用虚继承就可以解决掉这个问题。
当我们某个类(或者这个类的子类)有可能出现重复继承某个基类时,我们需要使用虚继承。
如下段代码:
class A{
int a;
};
class B : virtual public A{
int b;
};
class C : virtual public A{
int c;
};
class D : public B, public C {
int d;
};
编译后D的内存布局如下图,注意我们这边用的是vs的cl编译器编译后的结果,不同编译器对虚继承的实现也不一样
下面是虚函数表的内容
即使我们重复继承了对象A,但是在虚继承作用下还是只有一个类A的内存布局。在虚基类(使用了虚继承关键字的类)中有一个指针vbptr,这个指针指向一个虚继承表,表中记录着表距离类开始位置的偏移和公共变量距离vbptr的偏移(切记是相对于vbptr的偏移),比如B中距离类开始偏移为0,距离公共位置的偏移是20。当我们需要访问公共变量的时候,编译器就需要通过vbptr来寻找具体位置。
为什么虚继承要这样做呢?
为什么需要vbptr这种东西,类的成员变量在哪编译器应该知道的啊?其实vbptr和vfptr作用相似,子类指针类型赋值给一个父类的指针类型时才会展示出作用。
还是上面那一段代码中,其中类B的内存布局是
看下B中虚继承表的内容,第一个表示距离类开始的偏移,第二个值表示到公共变量的偏移
假设我们有一段代码,ptr1中保存的是D类的变量指针,ptr2保存的是B类的变量指针。
D d;
B *ptr1 = &d;
B b;
B *ptr2 = &b;
我们都用B类指针访问a类成员,在运行期间我们也不清楚这个指针指向的内存到底是什么类型,D类内存中和B类内存中需要偏移不同的值才能找到a变量。如果有虚继承表时我们先去查下偏移多少到a,B类中保存的是8,D类中保存的是20,这样就能准确的找到公共变量的位置了。
内容总结
以上是互联网集市为您收集整理的C++成员变量内存模型全部内容,希望文章能够帮你解决C++成员变量内存模型所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。