详解c++中的sturct

在c中struct只能存放数据,在c++中为其扩展了创建成员函数的功能,struct中的成员默认都是public的,struct的继承默认也是public,并且它是无法用于定义模板参数 ,这是它与class的主要区别。

虽然在c++中struct可以定义成员函数,不过我们还是将它主要用于定义一些 POD(plain old data),在 C++11 及之后的标准中,POD 类型需要同时满足两个独立条件:

  1. 平凡:类型具有默认的构造/拷贝/移动/析构函数(可自动生成且非虚)
  2. 标准布局(Standard Layout)​​ :内存布局与 C 兼容,成员排列顺序符合特定规则
    同时满足平凡性和标准布局的类型称为 POD 类型,这类数据可以安全使用 memcpy 等底层内存操作,因为它们的内存布局与 C 完全兼容且没有特殊处理需求。

内存对齐

内存对齐主要有三点原因,一是为了提高cpu的访问效率,cpu在读取时是按照块进行读取的,比如64位系统一般一次性读取8字节。二是早期某些平台不对齐访问会直接崩溃 或触发异常,三是为了保证原子操作,原子指令通常要求数据对齐,否则无法保证原子性。

内存对齐的基本规则:

  1. 每个成员 按照其自身大小对齐(char按1字节,int按4字节,double按8字节)
  2. 成员的起始地址必须是其对齐值的整数倍
  3. 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 字节
相关推荐
用户805533698033 小时前
不止三件套:QObject 属性系统全关键字与运行时反射!
c++·qt
BadBadBad__AK15 小时前
线段树维护区间 k 次方和
c++·数学·算法·stl
卷无止境1 天前
Eigen 库如何借助 OpenMP 加速计算
c++·后端
卷无止境1 天前
OpenMPI、MPICH 与 OpenMP:关系、核心概念与架构全解
c++·后端
郝学胜_神的一滴2 天前
CMake 30:循环语法全解|foreach_while双循环精讲、迭代技巧与实战避坑指南
c++·cmake
卷无止境4 天前
C++ 的Eigen 库全解析
c++
卷无止境4 天前
现代 C++特性大盘点:一门脱胎换骨的老语言
c++·后端
郝学胜_神的一滴4 天前
CMake 27:缓存变量的特性、语法、类型与实操全解
c++·cmake
博客18006 天前
酷宝的使用方法,超好用的免费界面库,C++、MFC可用
c++·mfc·界面库·库来帮·酷宝
郝学胜_神的一滴6 天前
CMake 026:属性体系精讲、四大作用域全解 & 实战代码落地
c++·cmake