一、结构体内存对齐:以空间换时间的设计

1. 内存对齐的核心规则

CPU 访问内存时并非逐个字节读取,而是按 “块”(如 4 字节、8 字节)读取。内存对齐通过牺牲少量内存空间,让数据存储在 CPU 高效访问的地址上,核心规则如下:

  1. 第一个成员从偏移量 0 的地址开始;
  2. 其他成员对齐到「编译器默认对齐数」和「成员自身大小」的较小值(对齐数)的整数倍地址;
  3. 结构体总大小为所有成员「最大对齐数」的整数倍;
  4. 嵌套结构体时,嵌套结构体对齐到自身最大对齐数的整数倍,整体大小为所有最大对齐数的整数倍。

注:VS 编译器默认对齐数为 8,GCC 默认对齐数为成员自身大小。

2. 实战解析:不同结构体的内存分布
示例 1:基础结构体
#include <stdio.h>

// 示例1:char + int
struct stu1 {
    char a;   // 偏移0,占1字节(1-3字节浪费)
    int b;    // 对齐数min(8,4)=4,偏移4开始,占4字节
}; // 总大小8(最大对齐数4,8是4的整数倍)

// 示例2:char + int + short
struct stu2 {
    char a;   // 偏移0,占1字节(1-3字节浪费)
    int b;    // 偏移4,占4字节
    short c;  // 对齐数min(8,2)=2,偏移8,占2字节(9-11字节浪费)
}; // 总大小12(最大对齐数4,12是4的整数倍)

// 示例3:优化成员顺序减少内存浪费
struct stu3 {
    char a;   // 偏移0,占1字节(1字节浪费)
    short b;  // 偏移2,占2字节
    int c;    // 偏移4,占4字节
}; // 总大小8(最大对齐数4,8是4的整数倍)

int main() {
    printf("stu1大小:%zd\n", sizeof(struct stu1));  // 输出8
    printf("stu2大小:%zd\n", sizeof(struct stu2));  // 输出12
    printf("stu3大小:%zd\n", sizeof(struct stu3));  // 输出8
    return 0;
}

关键结论:结构体成员顺序会影响内存占用!合理调整顺序(如将小类型成员集中)可减少内存浪费

示例 2:含数组 / 嵌套结构体的复杂场景
// 含数组的结构体
struct stu4 {
    char a[11];  // 偏移0-10,占11字节(11字节浪费)
    short c;     // 对齐数2,偏移12,占2字节(14-15字节浪费)
    double d;    // 对齐数8,偏移16,占8字节
    int b[2];    // 对齐数4,偏移24,占8字节
}; // 总大小32(最大对齐数8,32是8的整数倍)

// 嵌套结构体
struct stu5 {
    int a[3];          // 偏移0-11,占12字节
    char b[7];         // 偏移12-18,占7字节(19字节浪费)
    struct stu4 c[6];  // 嵌套结构体最大对齐数8,偏移32开始,6*32=192字节
    struct stu3* d;    // 指针大小4/8(64位系统8),对齐数8,偏移224开始
};

二、大小端存储:数据字节的排列方式

1. 大小端的定义
  • 小端存储:低字节数据存放在低地址,高字节数据存放在高地址(主流系统如 x86 架构默认)
  • 大端存储:高字节数据存放在低地址,低字节数据存放在高地址(网络传输、部分嵌入式系统常用)

例如:int a = 1(十六进制0x00000001):

  • 小端:内存布局 01 00 00 00
  • 大端:内存布局 00 00 00 01
2. 代码实现:判断系统大小端
方法 1:指针强制类型转换
#include <stdio.h>

void Test1() {
    int a = 1;
    // 将int*转为char*,仅读取第一个字节
    char* p = (char*)&a;
    // 遍历输出4个字节(验证内存布局)
    printf("内存字节布局:");
    for (int i = 0; i < 4; i++) {
        printf("%02x ", p[i]);
    }
    printf("\n");
    // 判断第一个字节是否为1
    if (*p == 1) {
        printf("小端存储\n");
    } else {
        printf("大端存储\n");
    }
}
方法 2:利用联合体(共用体)特性

联合体所有成员共享同一块内存,利用这一特性可简化判断:

#include <stdio.h>

union Un {
    char a;  // 占1字节
    int b;   // 占4字节(与a共享首地址)
};

void Test2() {
    union Un u;
    u.b = 1;  // 给int赋值,仅关注首字节
    if (u.a == 1) {
        printf("小端存储\n");
    } else {
        printf("大端存储\n");
    }
}

int main() {
    Test1();
    Test2();
    return 0;
}

三、开发实战注意事项

  1. 结构体传参优先用指针:结构体可能占用大量内存,值传递会拷贝整个结构体,指针传递仅拷贝地址(4/8 字节),提升效率
  2. 内存对齐的手动调整:可通过#pragma pack(n)修改默认对齐数(n 为对齐数),但需谨慎(可能降低 CPU 访问效率)
  3. 跨平台开发关注大小端:网络通信、文件存储时,需统一大小端(如将数据转为大端再传输)

四、完整代码汇总

#include <stdio.h>
#include <string.h>

// 基础结构体内存对齐示例
struct stu1 {
    char a;
    int b;
};

struct stu2 {
    char a;
    int b;
    short c;
};

struct stu3 {
    char a;
    short b;
    int c;
};

// 含数组的结构体
struct stu4 {
    char a[11];
    short c;
    double d;
    int b[2];
};

// 嵌套结构体
struct stu5 {
    int a[3];
    char b[7];
    struct stu4 c[6];
    struct stu3* d;
};

// 大小端判断-方法1:指针转换
void Test1() {
    int a = 1;
    char* p = (char*)&a;
    printf("内存字节布局:");
    for (int i = 0; i < 4; i++) {
        printf("%02x ", p[i]);
    }
    printf("\n");
    if (*p == 1) {
        printf("小端存储\n");
    } else {
        printf("大端存储\n");
    }
}

// 大小端判断-方法2:联合体
union Un {
    char a;
    int b;
};

void Test2() {
    union Un u;
    u.b = 1;
    if (u.a == 1) {
        printf("小端存储\n");
    } else {
        printf("大端存储\n");
    }
}

int main() {
    // 输出结构体大小
    printf("stu1大小:%zd\n", sizeof(struct stu1));
    printf("stu2大小:%zd\n", sizeof(struct stu2));
    printf("stu3大小:%zd\n", sizeof(struct stu3));
    printf("stu4大小:%zd\n", sizeof(struct stu4));
    
    // 判断大小端
    printf("\n=== 大小端判断 ===\n");
    Test1();
    Test2();
    
    return 0;
}

五、运行结果

stu1大小:8
stu2大小:12
stu3大小:8
stu4大小:32

=== 大小端判断 ===
内存字节布局:01 00 00 00 
小端存储

总结

  1. 结构体内存对齐的核心是「对齐数」,总大小为最大对齐数的整数倍,合理调整成员顺序可减少内存浪费
  2. 小端存储是主流架构(x86)的默认方式,可通过指针强制转换或联合体快速判断
  3. 结构体传参优先使用指针,跨平台开发需关注大小端和内存对齐的差异

本文代码已在 VS2022(Windows)、GCC(Linux)下验证,可直接复制运行

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐