C语言 | 结构体内存对齐

前言

本文中所有示例代码都是在64位linux系统下使用gcc编译。


目录

前言

1、默认对齐规则

2、修改对齐规则

2.1 #pragma pack(n)

2.2 __attribute__((packed))

3 位域结构体

3.1 相邻位域类型相同

3.2 相邻位域类型不同(和编译器有关)

3.3  位域和非位域穿插

参考文章


1、默认对齐规则

1. 第一个成员变量的偏移地址为0;

2. 其他成员变量要对齐到自身大小整数倍的地址处;

3. 结构体总大小为其成员变量所含最大类型的整数倍;

4. 嵌套的结构体对齐到自己成员变量所含最大类型的整数倍处,结构体的总大小是所有成员变量所含最大类型(含嵌套结构体)的整数倍。

例1:非嵌套结构体。

struct S1
{
    char c;
    int  i;
    double d;
}

分析:

根据规则1,变量c的偏移地址是0,占用1个字节;

根据规则2,变量i的偏移地址是4,占用4个字节;变量d的偏移地址是8,占用8个字节;

因此,结构体S1一共占用16字节。

例2:嵌套结构体。

struct S1
{
    char c;
    int  i;
    double d;
}

struct S2
{
   char c1;
   struct S1 s1;
   double d1;
}

分析:

根据规则1,变量c1的偏移地址是0,占用1个字节;

根据规则4,子结构体s1所含最大类型是double(占用8字节),因此,s1的偏移地址是8,又由上面的例1可知,s1占用16个字节;

根据规则2,变量d1的偏移地址是24;

因此,结构体S2一共占用32字节。


思考:为什么要进行内存对齐呢?

原因有以下两点:

1、平台原因

不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。

2、性能原因

数据结构(尤其是栈)应该尽可能在自然边界上对齐。原因在于,访问未对齐的内存时,处理器需要进行两次内存访问,而对于对齐的内存,只需要访问一次。

(注:图片取自此篇参考文章) [C语言](详细)结构体的内存对齐(规则、存在原因、默认对齐数的修改等+实例分析)_结构体内存对齐规则-CSDN博客


2、修改对齐规则

结构体的内存对齐可以理解成是拿空间换取时间,但是,在开发时我们为了节省内存空间,有时并不想让结构体按照上述规则进行默认对齐。修改对齐规则的方式有以下两种。

2.1 #pragma pack(n)

在需要修改对齐方式的结构体前面加上 #pragma pack(n),后面加上 #pragma pack(),即可让编译器按照n字节进行对齐。

例1:手动对齐数小于默认对齐数。

(1)默认对齐:占用8字节

struct S1
{
    char c;
    int  i;    //i的偏移地址为4
}

(2)手动对齐:占用5字节

#pragma pack(1)  //修改对齐数为4
struct S1
{
    char c;
    int  i;      //i的偏移地址为1
}
#pragma pack()   //结束修改对齐数

但是要注意,n应该小于默认对齐字节数。如果大于的话,编译器还是会按照默认对齐数进行对齐。也就是说,编译器会选择手动设置的对齐数和默认对齐数中的较小值。

例2:手动对齐数大于默认对齐数

 (1)默认对齐:占用4字节

struct S1
{
    char c;
    short s;  //s的偏移地址为2
}

(2)手动对齐:占用4字节

#pragma pack(4)  //修改对齐数为4
struct S1
{
    char c;
    short s;     //s的偏移地址仍然为2,不会修改为4
}
#pragma pack()   //结束修改对齐数

2.2 __attribute__((packed))

 __attribute__((packed)) 可以取消结构体内存对齐,按照最紧凑的方式排列。和 #pragma pack(1)的效果一致。

(1)默认对齐:占用8字节

struct S1
{
    char c;
    int  i;    //i的偏移地址为4
}

(2)手动对齐:占用5字节

//取消内存对齐
struct __attribute__((packed)) S1 
{
    char c;
    int  i;      //i的偏移地址为1
}


3 位域结构体

3.1 相邻位域类型相同

例1:相邻位域类型相同,位宽之和小于类型大小。进行压缩,后面字段紧邻前面字段。

 (1)默认对齐:占用1个字节

struct test
{
    char a:4;
    char b:2;
    char c:2;
}

(2)手动对齐:占用1个字节

#pragma pack(1)
struct test
{
    char a:4;
    char b:2;
    char c:2;
}
#pragma pack()

例2:相邻位域类型相同,位宽之和大于类型大小。不进行压缩,后面字段从新的存储单元开始。

(1)默认对齐:占用3个字节

struct test
{
    char a:6;
    char b:6;
    char c:4;
}

(2)手动对齐:占用2个字节

#pragma pack(1)
struct test
{
    char a:6;
    char b:6;
    char c:4;
}
#pragma pack()

3.2 相邻位域类型不同(和编译器有关)

例3: 相邻位域类型不同,位宽之和大于类型大小。进行压缩,后面字段紧邻前面字段。

(1)默认对齐:占用4个字节(规则3)

struct test
{
    char a:7;
    int b:11;
    int c:4;
    int d:2;
}

 (2)手动对齐:占用3个字节

#pragma pack(1)
struct test
{
    char a:7;
    int b:11;
    int c:4;
    int d:2;
}
#pragma pack()

 例4:相邻位域类型不同,位宽之和小于类型大小。进行压缩,后面字段紧邻前面字段。

(1)默认对齐:占用4个字节(规则3)

struct test
{
    int a:10;
    char b:6;
}

 (2)手动对齐:占用2个字节

#pragma pack(1)
struct test
{
    int a:10;
    char b:6;
}
#pragma pack()

3.3  位域和非位域穿插

例5:位域和非位域穿插时,不进行压缩,后面的字段从新的存储单元开始。

 (1)默认对齐:占用3个字节

struct test
{
    char a:6;
    char b;
    char c:2;
}

 (2)手动对齐:占用3个字节

#pragma pack(1)
struct test
{
    char a:6;
    char b;
    char c:2;
}
#pragma pack()

注意:对于例5,如果需要进行压缩,可以将【char b;】修改为【char b:8;】,则结构体占用字节数从3字节压缩为2字节。

例6:位域和非位域穿插时,不进行压缩,后面的字段从新的存储单元开始。

 (1)默认对齐:占用12个字节(规则2和规则3)

struct test
{
    char a:6;
    int  b;
    char c:2;
}

 (2)手动对齐:占用6个字节

#pragma pack(1)
struct test
{
    char a:6;
    int  b;
    char c:2;
}
#pragma pack()

参考文章

C语言 | 关于结构体内存对齐,看这篇就够了-腾讯云开发者社区-腾讯云 (tencent.com)

[C语言](详细)结构体的内存对齐(规则、存在原因、默认对齐数的修改等+实例分析)_结构体内存对齐规则-CSDN博客

C/C++编程笔记:C语言对齐问题【结构体、栈内存以及位域对齐】 - 哔哩哔哩 (bilibili.com)

C语言字节对齐及__attribute__((aligned(n))) 与 #pragma(pack(n))的作用-CSDN博客

  • 26
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值