C语言之内存对齐规则

C语言之内存对齐规则

考虑如下代码:

#include <stdio.h>
struct A
{
	short a;
	int b;
	char c;
};
struct B
{
	char a;
	short b;
	int c;
};
int main()
{
	printf("sizeof(struct A) = %d\n", sizeof(struct A));
	printf("sizeof(struct B) = %d\n", sizeof(struct B));
	return 0;
}

于是我疯狂脑补,输出应该是:

sizeof(struct A) = sizeof(short) + sizeof(int) + sizeof(char)
sizeof(struct B) = sizeof(char) + sizeof(short) + sizeof(int)

根据上面等式关系,我得出结论sizeof(struct A)等于sizeof(struct B)

然而现实给了我一个响亮的大嘴巴子。

实际的输出是这样的:

sizeof(struct A) = 12
sizeof(struct B) = 8

经高人指点,原来是编译器对数据结构进行了内存对齐。

内存对齐有以下四大规则:

  1. 结构体第一个成员的偏移量为0,之后的成员的偏移量是其自身成员类型所占字节大小的最小整数倍。
  2. 结构体内部成员完成内存对齐后,结构体本身还需进行一次内存对齐,保证结构体所占字节总数是该结构体内最大数据成员的最小整数倍。
  3. 如果结构体内存在结构体(联合体)成员,则结构体(联合体)成员的偏移量是其内部最宽基本类型成员的所占字节大小的整数倍。
  4. 如果程序中存在预编译指令#pragma pack(n),则强制所有字节数大于等于n的成员以n字节对齐(即偏移量是n的整数倍),无须考虑当前成员类型和结构体最大成员类型,其余成员任然按照规则1、2执行。

以上述规则为准,牛刀小试一把。

struct A                  struct B
{						  {
	short a;				  char a;
	int b;					  short b;
	char c;					  int c;
};						  };
数据类型大小
sizeof(int) = 4;
sizeof(char) = 1;
sizeof(short) = 2;
sizeof(double) = 8;

结构体A所占字节数计算:

​ 第一个数据成员a是short类型,占用2个字节,根据规则一,偏移量为0,占用范围0~1;

​ 第二个数据成员b是int类型,占用4个字节,根据规则一,偏移量为4的最小整数倍,即4,占用范围4~7;

​ 第三个数据成员c是char类型,占用1个字节,根据规则一,偏移量为1的最小整数倍,即8,占用范围8;

​ 此时,三个成员共占用了2 + 2(内存对齐所占字节) + 4 + 1 = 9个字节。根据规则二,为了保证结构体所占字节总数是其内部最大成员的最小整数倍,因此,结构体A所占字节总数必须是sizeof(int)(即4)的最小整数倍。

​ 由此,可以得出结构体A所占字节总数为9 + 3(内存对齐所占字节) = 12

同理,结构体B所占字节数计算:

​ 第一个数据成员a是char类型,占用1个字节,根据规则一,偏移量为0,占用范围0;

​ 第二个数据成员b是short类型,占用2个字节,根据规则一,偏移量为2的最小整数倍,即2,占用范围2~3;

​ 第三个数据成员c是int类型,占用4个字节,根据规则一,偏移量为4的最小整数倍,即4,占用范围4~7;

​ 此时,三个成员共占用了1 + 1(内存对齐所占字节) + 2 + 4 = 8个字节。根据规则二,结构体所占字节总数是其内部最大成员的最小整数倍。因此,结构体B所占字节总数必须是sizeof(int)(即4)的最小整数倍。

​ 由此,可以得出结构体B所占字节总数为8

难度升级,考虑如下结构:

struct C   
{
	short a;
	struct CC		
	{
		char n[11];	// 偏移量8
		double m;	// 偏移量24
	}cc;
	double b;		// 偏移量32
	char c;			// 偏移量40 
};

对于嵌套的结构体,应采取递归的方法计算,即先计算结构体CC,再计算结构体C。

​ 结构体CC:

​ 成员n相对于结构体CC偏移量为0,占用11个字节,占用范围0~11;

​ 成员m相对于结构体CC偏移量为8(sizeof(double))的最小整数倍,即16,占用范围16~23;

结构体C:

​ 成员a占用2个字节,偏移量为0,占用范围0~1;

​ 成员cc占用24个字节,根据规则二,偏移量为8(sizeof(double)),则相应地,cc.n的偏移量也为8,cc.m的偏移量为24,成员cc的占用范围8~31;

​ 成员d占用8个字节,偏移量为32,占用范围32~39;

​ 成员c占用1个字节,偏移量为33,占用范围40;

此时,四个成员共占用了2 + 6(内存对齐所占字节) + 24 + 8 + 1 = 41个字节。根据规则二,结构体所占字节总数是其内部最大成员的最小整数倍。因此,结构体B所占字节总数必须是sizeof(double)(即8)的最小整数倍。

由此,可以得出结构体CC所占字节总数为48 (41+7)

以上,我们了解了结构体内的基本类型成员以及结构体成员的内存对齐方式,可若是结构体内嵌套union,情况会怎么样呢?

struct D
{
	int a;             
	union DD
	{
		char n[11];    
		double m;
	}dd;
	double b;             
	int c;                 
};

sizeof(struct D) = 4 + 4(内存补齐) + 16 + 8 + 4 + 4(结构体补齐) = 40

结构体D:

​ 数据成员a是int类型,本身占用4个字节,偏移量为0,占用范围0~3;

​ 数据成员dd是union类型,本身占用11个字节,根据规则三,偏移量为8,占用范围8~23;

​ 数据成员b是double类型,本身占用8个字节,偏移量为24,占用范围24~31;

​ 数据成员c是int类型,本身占用4个字节,偏移量为32,占用范围32~35;

​ 根据规则二,结构体D所占字节总数必须是sizeof(double)(即8)的最小整数倍。

由此,可以得出结构体D所占字节总数为4 +4(int补齐)+11+5(union补齐)+8+4+4(结构体D补齐)=40

若是设置了#pragma pack(n)这种预编译指令实际上又会有什么不同呢?

#pragma pack(n)
struct F
{
	int a;
	char b;
	short c;
	double d;
};
  1. 当n = 1 时,表示强制内存按1个字节对齐,这种情况十分简单,直接将各个数据成员的实际大小直接相加即可(sizeof(struct F)=4+1+2+8=15)。

  2. 当n = 2 时,表示强制内存按2个字节对齐。

    数据成员a是int类型,本身占用4个字节,偏移量为0,占用范围0~3;

    数据成员b是char类型,本身占用1个字节,根据规则四,偏移量为2的整数倍,即4,占用范围4;

    数据成员c是short类型,本身占用2个字节,根据规则四,偏移量为6,占用范围6~7;

    数据成员d是double类型,本身占用8个字节,根据规则四,偏移量为8,占用范围8~16;

    因此,此时结构体F所占字节总数为4+1+1(char补齐)+2+8=16

  3. 当n = 4 时,表示强制内存按2个字节对齐。

    数据成员a是int类型,本身占用4个字节,偏移量为0,占用范围0~3;

    数据成员b是char类型,本身占用1个字节,根据规则四,偏移量为4的整数倍,即4,占用范围4;

    数据成员c是short类型,本身占用2个字节,根据规则四,short类型所占字节数小于4,须按照规则二执行,因此偏移量为6,占用范围6~7;

    数据成员d是double类型,本身占用8个字节,根据规则四,double类型所占字节数大于4,则偏移量为8,占用范围8~16;

    因此,此时结构体F所占字节总数为4+1+1(char补齐)+2+8=16

注意:

C99中定义了柔性数组机制,因此对于一个结构体,如果最后一个成员是数组的话,结构体大小与该成员是否是柔性数组有密切关系。

struct sds{
    unsigned int len;
    unsigned int free;
    char buf[0];//或char buf[]
};

当结构体定义中,最后一个成员是数组且数组大小为0或没标记时,该成员数组是柔性数组,不计入结构体大小,因此sizeof(sds) = 8

而下面的结构体sd的sizeof(sd) = 12,因为最后一个数组成员是普通数组,适用于上述补齐规则。

struct sds{
    unsigned int len;
    unsigned int free;
    char buf[1];
};

我们知道,C++为了兼容C,保留了struct关键字,但是实际上C++中的struct是一个默认访问控制权限为public的class。C++标准规定:一个空类的大小为1个字节,因此在C++中,sizeof(空类或空结构体) = 1,在C语言中,sizeof(空结构体) = 0。

参考资料

https://levphy.github.io/2017/03/23/memory-alignment.html

https://blog.csdn.net/liupeng900605/article/details/7530010?utm_medium=distribute.pc_relevant.none-task-blog-baidujs-3

https://blog.csdn.net/shi2huang/article/details/80290192

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值