一。 进程内存空间布局
// 不同的数据在内存中会有不同的 保存时机 保存位置
// 当执行一个可执行文件时,操作系统就会把这个可执行文件加载到内存,
// 此时进程有一个虚拟的地址空间(内存空间
最高内存地址
-------
堆栈段 : A a;
未映射区域
-------
堆 : new delete
-------
bss : 没有给初值,0的全局变量
数据段: 全局变量
-------
代码段
最低内存地址
------------------------------------------------------
普通成员变量的存储顺序 是按照在类中的定义顺序从上到下来的
后定义的普通成员变量的内存地址更高
类定义中的public private protected的数量 不影响类对象的sizeof
内存对齐
int a;
char c;
int b;
一共是12字节
一字节对齐: 告诉编译器不内存对齐
#pragma pack(1)
int a;
char c; // 这时候就是9字节
int b;
#pragma pack() // 取消值定对齐 恢复缺省对齐
------------------------------------------------------
成员变量偏移值的打印
成员变量偏移值 : 就这个成员变量的地址,离对象首地址偏移多少
&类名::成员名
------------------------------------------------------
class A{
public:
int a;
static int b;
}
int A::b = 0;
// 静态成员变量的存取
// 静态成员变量可以当成一个全局变量,但是他只在类的空间内可见 以用时 类名::静态成员变量名
// 静态成员变量只有一个实体,保存在可执行文件的数据段。
// 静态成员变量在编译时就已经分配好内存地址
------------------------------------------------------
// 非静态成员变量的存取 (普通成员变量 ) 存放在类的对象中,存取通过类对象(类对象指针)
// 对于普通成员的访问,编译器时把类对象的首地址加上成员变量的偏移量
// 如果有继承则先算父类中的偏移量 虚函数也是一样
------------------------------------------------------
单一继承下的数据成员布局
class A{
public:
int a;
int b;
}
class B: public A{
public:
int j;
int p;
}
int main(){
printf("A::a = %d\n",&A::a); // 父类中成员变量的偏移值
printf("A::b = %d\n",&A::b); // 0 4
printf("B::a = %d\n",&B::a); // 子类中的父类成员变量偏移值
printf("B::b = %d\n",&B::b); // 0 4
printf("B::j = %d\n",&B::j); // 子类中成员两边的偏移值
printf("B::p = %d\n",&B::p); // 8 12
}
// 一个子类对象 ,所包含的内容是他自己的成员加上他父类的成员的总和
// 从偏移值看 父类成员先出现 然后才是子类成员
------------------------------------------------------
class Base{
public:
int a;
char c1;
char c2;
char c3; // 8字节 内存对齐 内存紧凑 // 0 4 5 6
}
------------------------------------------------------
class Base {
public:
int a;
char c1;
}
class Base2 : public Base{
public:
char c2;
}
class Base3 : public Base{
public:
char c3;
}
int main(){
cout << sizeof(Base) <<endl; //8
cout << sizeof(Base2) <<endl; //12
cout << sizeof(Base3) <<endl; // 16
printf("Base3::a = %d\n",&Base3::a); // 0
printf("Base3::c1 = %d\n",&Base3::c1); // 4
printf("Base3::c2 = %d\n",&Base3::c2); // 8
printf("Base3::c3 = %d\n",&Base3::c3); // 12
// windows运行结果
}
// 引入继承关系后 可能会带来内存空间的额外增加 (边界调整
class Base {
public:
int a;
char c1;
}
class Base2 : public Base{
public:
char c2;
}
class Base3 : public Base2{
public:
char c3;
}
int main(){
cout << sizeof(Base) <<endl; //8
cout << sizeof(Base2) <<endl; //12
cout << sizeof(Base3) <<endl; // 12
printf("Base3::a = %d\n",&Base3::a); // 0
printf("Base3::c1 = %d\n",&Base3::c1); // 4
printf("Base3::c2 = %d\n",&Base3::c2); // 8
printf("Base3::c3 = %d\n",&Base3::c3); // 9
// Linux运行结果 g++ 5.4.0
}
------------------
// 内存布局情况 Linux
Base
int a; //4
char c1; //1
// 编译器填充三字节
------
Base2
int a; //4
char c1; //1
// 编译器填充三字节
char c2; // 1
// 编译器填充三字节
------
Base3
int a; //4
char c1; //1
// 编译器填充三字节
char c2; // 1 // windows则会继续填充三字节
char c3; // 1
// 填充2字节 // windows 填充三字节
------------------
// Linux和windows 上数据布局一样 说明编译器在不断进步和优化,
不同厂商编译器实现的细节也不一样
所以这时候就需要注意内存拷贝
// Linux下使用 memcpy 将base2内容直接拷贝base3上就会出问题
// Base3中的c3 有数据 copy base2 则会清空