1、C++结构体与类布局基本规则(编译器为VS2022)
C中结构体内存布局基本规则如下:
- 结构体变量的首地址能够被其最宽基本类型成员的大小所整除;
- 结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节(internal adding);
- 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一个成员之后加上填充字节(trailing padding)。
C++中类的起始地址似乎没有遵守第一条规则,目前测试发现其首地址总是4
的倍数。类对象的地址规定为类中第一个非静态变量的起始地址,因为静态变量不属于任何一个对象,它存放在全局区。
#include<iostream>
using namespace std;
class Animal {
public:
static int i;
int a=5;
double b;
void func1() {}
void func2() {}
};
int Animal::i = 5;
int main() {
Animal ani;
cout << &ani << endl; //0053FDB4
cout << &Animal::i << endl; //0058A000
cout << &ani.a << endl; //0053FDB4
cout << sizeof(Animal); //16,
return 0;
}
通过以下代码发现,C++中类的内存布局经过测试发现是完全遵守第二条规则的。
#include<iostream>
using namespace std;
class Animal {
public:
bool b_;
int a=5;
double b;
void func1() {}
void func2() {}
};
int main() {
Animal ani;
cout << sizeof(ani.b_) << endl; //1
cout << &ani.b_ << endl; //00BBF794
cout << &ani.a << endl; //00BBF798
cout << &ani.b << endl; //00BBF79C
return 0;
}
通过以下代码发现,C++中类的内存布局经测试发现是严格遵守第三条规则的。
#include<iostream>
using namespace std;
class Animal {
public:
bool b_;
int a=5;
double b;
void func1() {}
void func2() {}
};
int main() {
Animal ani;
cout << sizeof(ani) << endl; //16
return 0;
}
2、C++中带有虚函数的类布局
(1)观察C++中一个普通类的布局:
#include<iostream>
using namespace std;
class Animal {
public:
bool b_;
int a=5;
double b;
void func1() {}
void func2() {}
void func3() {}
void func4() {}
void func5() {}
};
int main() {
Animal ani;
cout << sizeof(ani) << endl; //16
return 0;
}
使用VS2022开发工具Developer PowerShell for VS 2019,在对应文件夹下输入命令cl /d1 reportSingleClassLayout类名 文件名.cpp
,可以得到类Animal
的内存结构图示:
图1:普通类的内存描述 |
---|
左边0、4、8表示属性相对起始位置的偏移字节数,<alignment member>(size=3)
表示为了对其而占用的三个字节。
(2)带有虚函数的类内存布局。
#include<iostream>
using namespace std;
class Animal {
public:
bool b_;
int a=5;
double b;
void func1() {}
virtual void func2() {}
virtual void func3() {}
virtual void func4() {}
virtual void func5() {}
};
int main() {
Animal ani;
cout << sizeof(ani) << endl; //24
return 0;
}
图2:带虚函数类的内存描述 |
---|
可以看到带有虚函数的类所占空间多了8个字节,这是因为在类起始位置多了vfptr
(虚函数(表)指针),它指向虚函数表(vftable
);vftable
内部记录了虚函数的入口地址。
(3)虚函数类的子类内存布局。
#include<iostream>
using namespace std;
class Animal {
public:
bool b_;
int a=5;
double b;
void func1() {}
virtual void func2() {}
virtual void func3() {}
virtual void func4() {}
virtual void func5() {}
};
class Cat :public Animal {
virtual void func3() {}
virtual void func4() {}
};
int main() {
Cat cat;
cout << sizeof(cat) << endl; //24
return 0;
}
图3:虚函数子类的内存描述 |
---|
可以看到在子类Cat
中,父类Animal
的属性被全部继承了下来,但是在子类Cat
的虚函数表中可以发现子类Cat
重写的函数其函数地址被换成了重写后的函数地址。当我们用一个父类Animal
指针指向子类Cat
对象,即Animal cat = new Cat()
之后,执行cat.func2()
语句,系统就会根据子类的虚函数表找到正确的子类重写后的函数。