1.结构体内存布局的基本规则

1.1.成员顺序

结构体的成员在内存中按照声明的顺序依次存放。第一个成员的地址与结构体的地址相同。

struct Example {
    char a;      // 1字节
    int b;       // 4字节  
    double c;    // 8字节
    short d;     // 2字节
};

1.2 对齐要求和结构体大小的计算

结构体的大小不是简单地将所有成员的大小相加,而是要考虑对齐和填充。每个基本类型(如int、double、指针等)都有对齐要求。对齐要求通常是该类型的大小(以字节为单位)或者是编译器特定的值。例如,在大多数系统上,int的对齐要求是4字节,double的对齐要求是8字节。

内存对齐:

内存对齐是通过在数据间填充少量字节,使数据的起始地址满足 CPU 字长(32 位为 4 字节、64 位为 8 字节)或数据自身大小的整数倍的内存布局规则,其核心意义在于:以牺牲少量内存形成内部碎片为代价,既大幅提升 CPU 按访问单元批量读取内存的效率(避免非对齐时多次读取、拼接数据的额外开销),又能兼容不支持非对齐访问的硬件(如早期 ARM 处理器)以防止程序崩溃,同时还能避免数据跨 CPU 访问单元存储引发的截断、乱码等问题,保障数据存储的完整性与稳定性。

默认对齐规则

(无 #pragma pack,编译器默认行为)

数据类型分类 对齐值计算规则 64 位系统示例(对齐值 / 大小) 32 位系统示例(对齐值 / 大小)
基本数据类型 对齐值 = 该类型的「字节大小」(大小就是 sizeof 的结果) char:1/1short:2/2int:4/4double:8/8 同 64 位(基本类型大小不变)
指针类型 对齐值 = CPU 字长(64 位 = 8,32 位 = 4),与指向的类型无关 int*:8/8char*:8/8double*:8/8 int*:4/4char*:4/4
数组类型 对齐值 = 「数组元素的对齐值」(数组整体大小不影响对齐值,只影响占用空间) char arr[5]:1/5int arr[3]:4/12double arr[2]:8/16 同 64 位(元素对齐值不变)
结构体(struct) 对齐值 = 其「所有成员对齐值的最大值」(和成员大小无关,只看成员的对齐值)

示例:

struct S {char c; double d;}对齐值 = max (1,8)=8

同 64 位(成员对齐值不变)
联合体(union) 对齐值 = 其「所有成员对齐值的最大值」(和成员大小无关,只看成员的对齐值)

示例:

union U {int a; char b[5];}对齐值 = max (4,1)=4

同 64 位(成员对齐值不变)
嵌套结构体 对齐值 = 该嵌套结构体「自身的对齐值」(而非嵌套结构体的大小)

示例:

struct Sub {char a; double b;}

// Sub对齐值 8

struct S {char c; Sub s;}

// S 的对齐值 = max (1,8)=8

同 64 位
计算规则:

规则1:如果你有一个成员_e(大小为x字节对齐值假设也为x字节)需要加入到相对结构体起始地址0偏移y byte的位置那么需要校验y是否为对齐值x的整数倍,若不为整数倍则需加最少的n字节,使得(y+n)%x = 0;
规则2: 结构体总大小必须是最大对齐值的整数倍, 而非成员大小的最大值. 
规则3: 若类有虚函数或者虚继承,则编译器会隐式地加一个虚函数指针的大小在开头;若继承多个不同类型的父类,则会继承到多个虚函数表指针. 这些虚表指针需要计入类的大小。

基本示例:
struct Example1 {
    char a;        // 1字节
    int b;         // 4字节
    double c;      // 8字节
}; // 由于内存对齐,可能是16字节

struct Example2 {
    char a;        // 偏移0
    double b;      // 偏移8(不能放在1)
    char c;        // 偏移16
}; // 总大小24字节(不是17,因为24是8的倍数)

// 内存布局:
// [a][填充7字节][b(8字节)][c][填充7字节]

class MyClass {
private:
    int a;         // 4字节
    double b;      // 8字节
    static int c;  // 不计入对象大小
public:
    void func() {} // 函数不计入对象大小
};

2.一些案例

关于嵌套结构体:

struct Inner {
    int x;      // 4字节
    char y;     // 1字节
    // 填充3字节
}; // 大小: 8字节

struct Outer {
    short a;        // 2字节
    // 填充2字节
    Inner inner;    // 8字节 (从4字节边界开始)
    char b;         // 1字节
    // 填充7字节
}; // 大小: 24字节

3.一个特殊用法!!!

对于任何一个结构体,可以获取到任何一个元素相较于结构体起点的偏移量:

&((struct A*)0->_element)

结构体指针指向的就是结构体的起点, 同时此起点有和起始元素的地址相同;

只要我们拥有结构体某一个元素的指针,那么就可以用这个指针加上偏移量得到整个结构体的指针

可以用预处理指令定义一个宏offsetof:

#define offsetof(TYPE, MEMBER)((size_t)&((TYPE *)0)->MEMBER)

4.大小端问题

结构体本身并不决定字节序(大端或小端)也不参与字节序, 字节序是由底层的硬件架构决定的。结构体的内存布局中, 成员按照声明顺序排列(考虑对齐), 但每个成员内部的字节顺序则取决于系统的字节序

大端存储(Big-endian):高位字节存储在低地址, 低位字节存储在高地址
小端存储(Little-endian):低位字节存储在低地址, 高位字节存储在高地址

/*
结构体内部遵循硬件平台的字节序

x86/x64架构:小端序

ARM(可配置):通常小端序

PowerPC、SPARC:大端序

网络字节序:大端序
*/

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐