前言
本次笔记记录如下知识点
- 数据成员绑定时机
- 进程内存空间布局
- 数据成员布局
- 单一继承下的数据成员布局
- 虚基类问题的提出和初探
- 成员变量地址,偏移与指针
一、数据成员绑定时机
成员函数函数体的解析时机
- 如果成员变量和全局变量重复了,那么成员函数在内部调用时,会优先调用成员变量,因为编译器对函数体内部的变量进行解析时,是从整个类定义完成之后进行的。
如下示例
string myvar;
class Base
{
public:
int myfunc()
{
return myvar;
}
private:
int myvar;
};
int main()
{
Base a;
return 0;
}
成员函数参数的确定时机
- 成员函数的参数类型是在编译器最近一次遇到指定类型来决定,它是本着最近碰到类型的原则来应用最近的碰到的类型
- 如果将下方示例代码中类的
typedef int myvar; myvar m_value;两行代码放到函数下方定义,那么程序就会因为myfunc函数中的赋值代码出现类型不匹配导致程序错误。
typedef string myvar;
class Base
{
private:
typedef int myvar;
myvar m_value;
public:
int myfunc(myvar var)
{
m_value = var;
}
};
int main()
{
Base a;
return 0;
}
二、进程内存空间布局
- 一个可执行文件加载到内存后,就组成了一个进程,进程中的虚拟地址空间有如下几部分
1、栈:局部变量等放在这里。
2、堆:new,malloc等申请的内存空间从这里分配。
3、BSS段:未初始化的全局变量,初始化为0的全局变量等放在这里。
4、数据段:已初始化的全局变量,该数据段包含了BSS段。
5、代码段:存放程序执行代码的一块内存区域。
- 内存分布如下图所示:

三、数据成员布局
边界调整与字节对齐
- 在类中,每个成员变量的地址都是连续排列的。例如:每个变量都是4个字节,那么变量之间的地址空隙就是4个字节
- 如果有一些因素导致成员排列不连续,成员的边界会进行调整的,例如:类中所有成员变量都是4个字节,那么突然创建了一个1个字节的变量,此时,边界就会调整这个1字节变量占用的内存,使它成为4或者8的整数倍。
如下示例
class Base
{
public:
int a;
int b;
char c;
int d;
};
int main()
{
Base a;
a.a = 0;
a.b = 0;
a.c = '0';
a.d = 0;
cout<<"a地址:" << &a.a <<endl;
cout<<"b地址:" << &a.b <<endl;
cout<<"c地址:" << &a.c <<endl;
cout<<"d地址:" << &a.d <<endl;
return 0;
}
运行结果
- 通过 如上例子我们可以发现,边界为我们对齐了代码中char类型变量的字节。
- 在某一些情况下,我们不需要这样的对齐方式,那么可以采用一字节对齐,就是针对该类的结构,要求其各个成员之间紧密排列。可以使用如下结构
#pragma pack(1)
//代码结构采用一字节对齐
#pragma pack
四、单一继承下的数据成员布局
在单一继承下,一个子类对象包含的内容,是它自己的成员加上父亲成员的总和。且从成员变量布局来看,父亲成员是先出现的,然后才是子类成员。
通过下面例子分析成员布局
class Base1
{
public:
int m_i1;
char m_c1;
};
class Base2 : public Base1
{
public:
char m_c2;
};
class Base3 : public Base2
{
public:
char m_c3;
};
int main()
{
printf("Base1:%d\n",sizeof(Base1));
printf("Base2:%d\n",sizeof(Base2));
printf("Base3:%d\n",sizeof(Base3));
printf("Base1:m_i1 = %d\n",&Base1::m_i1);
printf("Base1:m_c1 = %d\n",&Base1::m_c1);
//这里类的内存空间是以4字节对齐的
printf("Base2:m_i1 = %d\n",&Base2::m_i1);
printf("Base2:m_c1 = %d\n",&Base2::m_c1);
printf("Base2:m_c2 = %d\n",&Base2::m_c2);
return 0;
}
运行结果
五、虚基类的问题和初探
虚基类的问题
- 虚基类是具有三层结构,有一个爷爷类,两个父类,一个子类,如下图所示:

- 如上图所示,在孙子类B中,就只会包含一份Grand类的子对象,因为类A1和类A2虚继承Base类。
- 虚继承的存在就是为了解决多重继承时保证Base类的变量在B类中不会多次出现。
通过如下例子分析
class Base1
{
public:
int m_i1;
};
class Base2 :virtual public Base1
{
};
class Base3 :virtual public Base1
{
};
class B :public Base2,public Base3
{
};
int main()
{
B b;
b.m_i1 = 12;
return 0;
}
虚基类的初探
- 在虚基类中,父类虚继承爷爷类会在父类中插入一个虚基类表指针,从而影响父类的内存大小。
- 在孙子类继承父类的时候,也同样会继承父类的虚基类表指针,从而导致孙子类会多出8个字节的内存空间大小。
- 虚基类与常规的继承不同地方在于对象中的数据布局不同。根据上面的例子画出孙子类B的内存布局。

六、成员变量地址,偏移与指针
1、可以用一个指针来指向对象的成员变量。
2、成员变量指针里面保存的其实是偏移值,不是真正的内存地址。
3、未经初始化的成员变量指针,编译器给的默认值是0Xcccccccc,当给它赋值成0或者NULL,调试时可以观察到其值是0xFFFFFFFF,但是输出结果的化就是-1。
如下示例
class MYACLS
{
public:
int m_i;
int m_j;
int m_k;
};
int main()
{
//成员变量指针
int MYACLS::*mypoint = &MYACLS::m_i;
int MYACLS::*mypoint2 = 0;
printf("mypoint = %d\n",mypoint);
printf("mypoint2 = %d\n",mypoint2);
return 0;
}
运行结果如下
版权声明:本文为qq_40223389原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。