在c中struct只能存放数据,在c++中为其扩展了创建成员函数的功能,struct中的成员默认都是public的,struct的继承默认也是public,并且它是无法用于定义模板参数 ,这是它与class的主要区别。
虽然在c++中struct可以定义成员函数,不过我们还是将它主要用于定义一些 POD(plain old data),在 C++11 及之后的标准中,POD 类型需要同时满足两个独立条件:
- 平凡:类型具有默认的构造/拷贝/移动/析构函数(可自动生成且非虚)
- 标准布局(Standard Layout) :内存布局与 C 兼容,成员排列顺序符合特定规则
同时满足平凡性和标准布局的类型称为 POD 类型,这类数据可以安全使用 memcpy 等底层内存操作,因为它们的内存布局与 C 完全兼容且没有特殊处理需求。
内存对齐
内存对齐主要有三点原因,一是为了提高cpu的访问效率,cpu在读取时是按照块进行读取的,比如64位系统一般一次性读取8字节。二是早期某些平台不对齐访问会直接崩溃 或触发异常,三是为了保证原子操作,原子指令通常要求数据对齐,否则无法保证原子性。
内存对齐的基本规则:
- 每个成员 按照其自身大小对齐(
char按1字节,int按4字节,double按8字节) - 成员的起始地址必须是其对齐值的整数倍
- struct整体大小必须是最大对齐值的整数倍
举例:
cpp
struct Example1 {
char a; // 偏移0,占用1字节
// 填充7字节(让double从8的倍数开始)
double b; // 偏移8,占用8字节
char c; // 偏移16,占用1字节
// 填充7字节(使总大小为8的倍数)
}; // 总大小:24字节
struct Example2 {
double b; // 偏移0,占用8字节
char a; // 偏移8,占用1字节
char c; // 偏移9,占用1字节
// 填充2字节(偏移10-11)← int 需要4字节对齐
int d; // 偏移12,占用4字节 ← 10不是4的倍数,要从12开始
}; // 总大小:16字节 ✓
struct Example3 { // 从大到小排列
double b; // 偏移0,占用8字节
int d; // 偏移8,占用4字节
char a; // 偏移12,占用1字节
char c; // 偏移13,占用1字节
// 填充2字节(偏移14-15),使总大小为8的倍数
}; // 总大小:16字节
在内存对齐的情况下,我们想要访问一个数据cpu都只需要读取一次内存,假设没有内存对齐,拿Example1的成员顺序举例,内存的前8字节会存放一个完整的char a(1字节)和double b的前7字节。如果我们想要访问这个double b,cpu就需要访问两次内存才能拼凑出完整的数据。
为了提高运行效率和确保稳健性一般建议开启内存对齐,小建议:
- 按从大到小 的顺序声明成员,可以减少内存浪费(参考Example3)
在一些嵌入式设备中,内存是比较宝贵的,内存对齐虽然可以提高运行效率,但会浪费一些内存空间,这时候我们可以通过#pragma pack来关闭内存对齐,用时间换空间。
cpp
// 正常对齐(默认)
struct Normal {
char a; // 1字节
// 填充7字节
double b; // 8字节
char c; // 1字节
// 填充7字节
}; // 总大小:24字节
// 1字节对齐,取消填充
#pragma pack(1)
struct Packed1 {
char a; // 1字节
double b; // 8字节
char c; // 1字节
}; // 总大小:10字节
#pragma pack() // 恢复默认对齐
// 2字节对齐
#pragma pack(2)
struct Packed2 {
char a; // 1字节
// 填充1字节
double b; // 8字节(按2字节对齐,每次最多填充1字节)
char c; // 1字节
// 填充1字节
}; // 总大小:12字节
#pragma pack()
// 4字节对齐
#pragma pack(4)
struct Packed4 {
char a; // 1字节
// 填充3字节
double b; // 8字节(按4字节对齐)
char c; // 1字节
// 填充3字节
}; // 总大小:16字节
#pragma pack() //恢复正常对齐
int main() {
printf("Normal: %zu 字节\n", sizeof(Normal));
printf("Pack(1): %zu 字节\n", sizeof(Packed1));
printf("Pack(2): %zu 字节\n", sizeof(Packed2));
printf("Pack(4): %zu 字节\n", sizeof(Packed4));
return 0;
}
//输出
Normal: 24 字节
Pack(1): 10 字节
Pack(2): 12 字节
Pack(4): 16 字节