基本数据类型变量 结构体 union 类的内存字节对齐

本文详细解释了内存字节对齐的含义、原因和重要性,以及介绍了基本数据类型、结构体和类对象的字节对齐规则。通过实例解析了结构体成员变量的内存布局,并探讨了union的字节对齐和内存布局原则。理解这些规则对于提高程序运行效率至关重要。

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

系原创,转载时请帖上地址:http://blog.youkuaiyun.com/qq51931373/article/details/45843975


什么叫内存字节对齐:就是在内存地址中按照一个规则(这个规则具体是什么在下面说)把一个变量放在它应该在的内存地址,而不是把变量从前到后一个紧挨着一个存放(那是 在理想状态下存在的,而理想和现实是有差距的,呵呵)。


为什么要字节对齐    :CPU从数据总线中读取内存中的数据,而CPU一次性从内存中读取多少位的数据是由数据总线的位数来决定的,拿i386处理器来说,它的数据总线是32位,也就决定了i386的处理器一次性是从内存中读取32位的数据(也就是4个字节)。如果在内存中的变量在存放的时候没有严格按照规则(下面会讲) 被存放到一个它应该在的内存地址,就会导致读取一个变量的值到CPU时,会读取多次,这就导致了效率上的下降,由于CPU的速度比内存的速度快很多倍,所以CPU从内存中读一次  和 从内存中读两次 从CPU的角度来说这个效率的下降是不可以接受的.而为什么没有对齐的话则会读两次甚至三次,请阅读这篇客:http://blog.youkuaiyun.com/qq51931373/article/details/45846001


 以下介绍基本数据类型变量  结构体  类对象的字节对齐规则:

在介绍规则之前先明确以下概念:

1. 基本数据类型变量的自身对齐值 = sizeof(type) ,如int 类型变量的内存对齐值就是4。如果int a[10] 则变量a的自身对齐值仍然是sizeof(int),而不是sizeof(a)*10

2. 结构体变量 或 类对象的自身对齐值 = 结构体或者类中的所有数据成员中自身对齐值最大的那个值.而数据成员的自身对齐值是根据第一条得来的。

3.显示指定的对齐值 ,比如手动写pragma pack(4) 这个4就是指定的对齐值,不写这段代码则是采用编译器的默认对齐值.

4.基本数据类型变量的有效对齐值 = min(基本数据类型变量的自身对齐值,显示指定的对齐值或编译器默认对齐值)

   结构体变量的有效对齐值 = min(结构体变量的自身对齐值,显示指定的对齐值或编译器默认对齐值)

   类对象的有效对齐值  = min(类对象的自身对齐值,显示指定的对齐值或编译器默认对齐值)


所以:

规则1.基本数据类型变量存放的初始地址 就应该是基本数据类型变量的有效对齐值的整倍数 。

           比如: 假设默认对齐值或者指定对齐值是4 ,那么 :char c; int a[10]; 则a的初始地址一定是在c的初始地址往后移动4个字节,假设c的地址是0x00000000则a的地址就是       0x00000004,而0x00000004是a的有效对齐值的整倍数


规则2.结构体变量或者类对象所占内存字节数的多少应该是结构体变量或者类对象的有效对齐值的整倍数.

          比如:

#pragma  pack(8)
typedef struct T
{
	char c[10]; //本身长度1字节 
	__int64 d;  //本身长度8字节
	
	int e;  //本身长度4字节
	short f;  //本身长度2字节
	char g;  //本身长度1字节
	short h;  //本身长度2字节
};

struct T t1;

sizeof(t1) = 40;

为什么是40?在下一一道来:假设结构体T的初始地址是0x00000000

==>先计算第一个成员变量c的初始地址.c的自身对齐值是sizeof(char) = 1.指定对齐值是8,取两者最小,则c的有效对齐值是1,所以c的初始地址必须是1的整倍数,而0x00000000是1的整倍数,所以c被放在0x00000000处,并占用0x00000000-0x00000009这10个位置.

==>然后计算成员变量d的初始地址.d的自身对齐值是sizeof(__int64)=8.指定对齐值是8,取两者最小,则d的有效对齐值是8,所以d的初始地址必须是8的整倍数,而c占用了前面10个位置,所以d只能从0x0000000A(对应十进制10)位置开始,但是0x0000000A不是8的整倍数,所以只能往后移,一直移动到0x00000010(对应十进制16),并占用0x00000010-0x00000017

==>然后计算成员变量e的初始地址.e的自身对齐值是sizeof(int) = 4,指定对齐值是8,取两者最小,则e的有效对齐值是4,所以e的初始地址必须是4的整倍数,因为d占用到了0x00000017这个位置,所以接下来空的位置是0x00000018(对应十进制24),而这个位置刚好是4的整倍数所以e就占用0x00000018-0x0000001b

==>然后计算成员变量f的初始地址.f的自身对齐值是sizeof(short) = 2,所以f的有效对齐值是2.f占用的地址区间应该是0x0000001c-0x0000001d

==>g占用的一个字节,它的初始地址就是0x0000001e

==>h占用的地址区间是0x00000020-0x00000021

到目前位置从0x00000000 - 0x00000021是结构体所有成员变量所占用的内存地址区间,一共34个字节。


那么这个结构体变量占用的内存真的就是34个字节么?

结构体的最终大小应该是结构体有效对齐值的整倍数,结构体有效对齐值 = 8,所以为了是8的倍数就必须往后面再填充6个字节,最终大小为40个字节。


类对象的字节对齐原则和结构体变量的字节对齐原则是一样的,所以就不多说了。

下面是一些关于这方面自己认为比较好的博客:

http://zhangyu.blog.51cto.com/197148/673792/

http://www.cnblogs.com/tujunyan/archive/2009/05/23/1487934.html

http://blog.youkuaiyun.com/qq51931373/article/details/45846001


有错误的地方欢迎指正。


_____________________________________________________华丽的分割线____________________________________________________________

union变量的数据成员在内存中是互斥排他存在的,意思就是同一时刻union变量的内存中只会存在一个数据成员的值,所以在编程过程中如果一个集合中的两个或者多个变量存在互斥的寓意则可以考虑使用union.

union的字节对齐 和 内存布局:

 只要记住两个原则就可以了:

第一:union的内存的大小是由成员变量中占用字节数最多的那个初步决定的(为什么是初步决定)。

             比如: union { char c[1024]; uint32 i;}则这个union的内存大小初步估计是1024,为什么是初步估计?因为最终大小还要受第二个原则的影响。

第二:第一原则中得到的初步大小   必须是    union中每一个数据成员(除开占用内存最多的那个数据成员)的有效字节对齐值(它如何得到参看上面的struct部分)的整倍数。

 

举两个例子:

 第一个例子:

           

typedef union MyUnion
{
	char c[14];
	UINT32 i;
};
sizeof(MyUnion) = 16;

为什么是16?c占用14所以初步大小是14,14是i的有效字节对齐值的整倍数吗?  i的有效字节对齐值是4=min(自身字节对齐值4,我的编译器默认字节对齐值8).。明显14不是4的倍数,所以往后移动2个字节当然就是16


第二个例子:

#pragma  pack(2)//指定对齐值2,把编译器默认字节对齐值覆盖掉
typedef union MyUnion
{
	char c[14];
	UINT32 i;
};

sizeof(MyUnion) = 14;

为什么是14?c占用14所以初步大小是14,i的有效字节对齐值是2=min(自身字节对齐值4,指定字节对齐值2).14是2的倍数吗?当然是咯。



—————————————————————————————————华丽的分割线-----------------------------------------------------------------------------------


下面是几个稍微复杂的例子,对他们理解之后,那么字节对齐就彻底搞懂了。


第一个:

typedef struct Iner
{
	char c;
	UINT32 Iner_Int;
	short s;
}sIner;

typedef struct Outer
{
	UINT32 Outer_Int;
	__int64 i64 ;
	sIner inter;
	char c;
}sOuter;

sizeof(sOuter) = 32;


第二个:

typedef struct
{
	char a1;
	short a2;
	char a3;
}st_a;
typedef union
{
	long a;
	st_a ta;
}un_a;

sizeof(un_a) = 8;


就运用上面的原则就可以知道为什么结果是这样,如果还是不明白的,在我博客留言。


### 结构体内存对齐的基本原理 C语言中结构体内存布局并不是简单地按照成员变量的顺序连续排列,而是受到**内存对齐规则**的影响。内存对齐的主要目的是提高CPU访问内存的效率,尤其是在访问未对齐的数据时可能会导致性能下降或硬件异常。 结构体的对齐规则包括以下几点: 1. **成员变量的起始地址必须是其型对齐数的整数倍**。例如,`int` 型通常要求其起始地址是4的倍数。 2. **结构体的总大小必须是其所有成员变量中最大对齐数的整数倍**。如果最后的填充不够,则会在结构体末尾添加填充字节。 例如,考虑以下结构体: ```c struct MyStruct { int a; // 4字节 char b; // 1字节 int c; // 4字节 }; ``` 该结构体内存布局如下所示: ``` +-------+-------+-------+-------+-------+-------+-------+-------+ | a | a | a | a | b | pad1 | pad2 | pad3 | +-------+-------+-------+-------+-------+-------+-------+-------+ | c | c | c | c | | | | | +-------+-------+-------+-------+-------+-------+-------+-------+ ``` - `int a` 占用4字节,起始地址为0,符合对齐要求。 - `char b` 占用1字节,起始地址为4,符合对齐要求。 - `int c` 需要4字节对齐,但由于 `b` 后面只有1个字节,不足以满足 `int` 的对齐要求,因此在 `b` 后面插入3个填充字节。 - 结构体总大小必须是 `int` 型(最大成员)对齐数的整数倍,即8字节的整数倍。最终大小为8字节 + 4字节 = 12字节[^2]。 --- ### 结构体对齐与填充示例 以下是一个更直观的例子: ```c struct Example { char a; // 1字节 int b; // 4字节 short c; // 2字节 }; ``` 根据对齐规则,其内存布局如下: ``` +-------+-------+-------+-------+-------+-------+-------+-------+ | a | pad1 | pad2 | pad3 | b | b | b | b | +-------+-------+-------+-------+-------+-------+-------+-------+ | c | c | pad4 | pad5 | | | | | +-------+-------+-------+-------+-------+-------+-------+-------+ ``` - `char a` 占1字节,起始地址为0,符合对齐要求。 - `int b` 需要4字节对齐,因此在 `a` 后面填充3个字节。 - `short c` 需要2字节对齐,当前地址为8,刚好是2的整数倍,无需填充。 - 结构体总大小为12字节,是最大成员(`int`)对齐数的整数倍[^2]。 --- ### 联合体的内存对齐 联合体(union)的内存大小取决于其最大成员的大小,并且需要对齐到该最大成员的对齐数。例如: ```c union example3 { float x; // 4字节 char c[5]; // 5字节 } w; ``` 该联合体的最大成员是 `char c[5]`,其大小为5字节。但由于联合体的对齐要求为4字节(`float` 的对齐数),最终联合体的大小为8字节(向上取整到4的整数倍)[^1]。 --- ### 内存对齐的图示总结 以下是一个结构体内存布局图示: ``` struct MyStruct { int a; // 4字节 char b; // 1字节 int c; // 4字节 }; ``` 对应的内存布局为: ``` +-------+-------+-------+-------+-------+-------+-------+-------+ | a | a | a | a | b | pad1 | pad2 | pad3 | +-------+-------+-------+-------+-------+-------+-------+-------+ | c | c | c | c | | | | | +-------+-------+-------+-------+-------+-------+-------+-------+ ``` - `a` 占用4字节,地址为0。 - `b` 占用1字节,地址为4。 - `c` 需要4字节对齐,因此在 `b` 后面插入3个填充字节。 - 结构体总大小为12字节,是最大成员对齐数(4)的整数倍[^2]。 --- ### 相关问题
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值