结构体的内存对齐

本文详细解析了结构体内存对齐的规则及其原理,包括结构体成员的对齐方式、如何计算偏移量以及如何调整默认对齐数。同时探讨了内存对齐对性能的影响及如何优化结构体布局来节省空间。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

结构体内存对齐的规则

  1. 结构体的第一个成员直接对齐到相对于结构体变量起始位置为0的偏移处。
  2. 从第二个成员开始,要对齐到某个【对齐数】的整数倍的偏移处。
  3. 对齐数:结构体成员自身大小和默认对齐数的较小值(vs的默认值是8)(Linux没有默认对齐数,对齐数就是结构体成员的自身大小)
  4. 结构体的总大小,必须是最大对齐数的整数倍。每个结构体成员都有一个对齐数,其中最大的对齐数就是最大对齐数。
  5. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

举个例子

struct tagTest1
{
    short a;
    char d;
    long b;
    long c;
};
int main()
{
    struct tagTest1 stT1;

    printf("%d“, sizeof(stT1)); //12
    return 0;
}

short a因为是第一个成员,所以直接从结构体变量起始位置为0的偏移处开始。(short的对齐数是2)
char d是第二个成员,要对齐到char类型的整数倍的偏移处。(char的对齐数是1)
long d,c跟char一样,要对齐到自己类型的整数倍的偏移处。(long的对齐数是4)
红色块的就是浪费的空间,虽然内存不要钱,但是为啥要浪费呢?我后面会讲为啥要浪费。
所以tagTest1的大小是12字节,0-11(0也是一个字节)所以一共是12个字节,12是tagTest1里面最大对齐数的倍数。

下面是嵌套了结构体的代码

struct tagTest1
{
    short a;
    struct tagTest2 stT2;
    char d;
    long b;
    long c;
};
struct tagTest2
{
    long b;
    short c;
    char d;
    long a;
};
int main()
{
    struct tagTest1 stT1;

    printf("%d", sizeof(stT1));
    return 0;
}

short a因为是第一个成员,所以直接从结构体变量起始位置为0的偏移处开始。(short的对齐数是2)
第二个成员是结构体,对齐到他的最大对齐数的整数倍的偏移量,然后加上他自己的大小。
char d要对齐到char类型的整数倍的偏移处。(char的对齐数是1)
long d,c跟char一样,要对齐到自己类型的整数倍的偏移处。(long的对齐数是4)
所以stT1的大小是28字节,0-27,(0也算一个字节)28是stT1最大对齐数的倍数。
红色是浪费的内存空间。

如何知道某个结构体成员变量相对于结构体起始位置的偏移量呢

我们可以使用offsetof这个宏,他的第一个参数是结构体的变量名,第二参数是结构体的成员名。不懂得可以看上面那个图

struct tagTest2
{
    long b;
    short c;
    char d;
    long a;
};
struct tagTest1
{
    short a;
    struct tagTest2 stT2;
    char d;
    long b;
    long c;
};
int main()
{
    struct tagTest1 stT1;

    printf("%d %d %d %d %d", 
        offsetof(struct tagTest1, a), //第一个参数是结构体的变量名,第二个是结构体的成员名。
        offsetof(struct tagTest1, stT2),
        offsetof(struct tagTest1, d),
        offsetof(struct tagTest1, b), 
        offsetof(struct tagTest1, c)) ;
    //运行结果是0 4 16 20 24 
    return 0;
}

修改默认对齐数

#pragma pack(4) //设置默认对齐数
//#pragma pack() 恢复默认对齐数
struct tagTest2
{
    long b;
    short c;
    char d;
    long a;
};
int main()
{
    struct tagTest2 stT2;

    printf("%d", sizeof(stT2)); //11
    return 0;
}

第一个老规矩是从0偏移量开始的
因为我把默认对齐数设为1,所以没有一个空间浪费,所以这次的结构体一共是11个字节,为啥捏,因为对齐数是结构体成员自身大小和默认对齐数的较小值,而1比他们都小(除了char)。所以对齐数变成了1,这次结构体也是11字节,而不是12字节。

为啥要存在内存对齐

  1. 平台原因(移植原因)

大部分的参考资料都是如是说的

不是所有的硬件平台都能访问任意地址上的任意数据;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常
** 2. 性能原因:**
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。
原因在于,为了访问未对齐的内存,处理器需要作两次内存访问,而对齐的内存访问仅需要一次访问。(所以会有被浪费的内存)
总体来说

结构体的内存对齐是拿空间换取时间的做法。

那在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到呢

让占用空间小的成员变量集中在一起

就像这样

struct tagTest2
{
    char d;
    int  c;
    char a;
};
struct tagTest1
{
    char a;
    char d;
    int  c;
};
int main()
{
    struct tagTest2 stT2;
    struct tagTest1 stT1;

    printf("%d %d", sizeof(stT2),sizeof(stT1)); //stT2=12 stT1=8
    return 0;
}

第一个就是8字节 第二个就是12字节

xdm写的不好,如果大家觉得我有说得不对的话,欢迎大家批评指导。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值