结构体内存对齐和大小端存储
1. 基本数据类型
C语言中基本数据类型包括
char
、short
、int
、long
、long long
、float
、double
等,都是可以直接使用的,在VS集成开发环境下,所占空间大小(字节)如下:
#include <stdio.h>
int main()
{
printf("%d\n", sizeof(char)); // 1
printf("%d\n", sizeof(short)); // 2
printf("%d\n", sizeof(int)); // 4
printf("%d\n", sizeof(long)); // 4
printf("%d\n", sizeof(long long)); // 8
printf("%d\n", sizeof(float)); // 4
printf("%d\n", sizeof(double)); // 8
return 0;
}
[!NOTE]
标准规定
sizeof(long)
>=sizeof(int)
。
- 这些基本数据类型想要表达单个属性,比如年龄、体重、身高是可以办到的。
- 但想描述一个复杂个体,是办不到的,这就需要使用自定义数据类型。
2. 结构体数据类型
-
语法:
-
struct s { int i; char ch; double d; };
-
可以看作一个或多个数据类型构成的集合体。
-
3. 结构体的内存对齐
- 既然结构体是由一个或多个数据类型构成的集合体,那么所占用的内存空间应该和基本数据类型创建的变量不一样,如上述结构体内包含
char
、int
、double
三种基本数据类型,把这三种数据类型所占大小相加得到 13 13 13,然而事实真的如此吗?
#include <stdio.h>
struct s
{
int i;
char ch;
double d;
};
int main()
{
printf("%d\n", sizeof(struct s)); // 16
return 0;
}
- 程序的结果是 16 16 16,也就说明结构体的内存占用并不只是成员变量内存占用。
- 在C语言中,结构体存在内存对齐,是为了提高内存访问效率而设计的机制。
3.1 对齐规则
-
成员对齐
第一个成员变量从偏移量**
0
位置开始占用内存空间,其余每个成员的起始地址必须是其对齐数**的整数倍。- 对齐数 =
min(成员类型的大小, 编译器默认对齐数)
。 - 默认对齐数通常由编译器决定,可通过
#pragma pack(n)
修改(此时对齐数为min(成员类型大小, n)
)。
- 对齐数 =
-
结构体总大小
结构体的总大小必须是其所有成员中最大对齐数的整数倍。
- 若由嵌套结构体,最大对齐数可能包含嵌套结构体的最大对齐数。
-
结构体作为成员时的对齐
当结构体作为其他结构体的成员时,其起始地址必须是自身最大对齐数的整数倍。
3.2 示例分析
3.2.1 基本数据类型
struct s
{
int i;
char ch;
double d;
};
- 内存布局(VS集成开发环境):
int i
从偏移量**0
**的位置开始计算,所占4个字节,默认对齐数为8,取最小值4字节。char ch
所占1个字节,默认对齐数为8,取最小值1字节,偏移量4是1的倍数,因此ch
占偏移量4位置的内存。double d
所占8个字节,默认对齐数为8,取最小值8字节,而偏移量5不是8的倍数,所以填充3字节,d
占偏移量8-15位置的内存。- 整体字节数0-15为16字节,是最大对齐数8的倍数,因此该结构体共16字节。
3.2.2 嵌套结构体对齐
struct s1
{
char c; // 1字节(对齐数1)
int i; // 4字节(对齐数4)
}; // 总大小:1 + 3(填充)+ 4 = 8字节(是4的倍数)
struct s2
{
char cc;
struct s1 s;
short ss;
};
- 内存布局(VS集成开发环境):
cc(1) + [填充3] + s1(8) + ss(2) + [填充2]
- 总大小: 1 + 3 ( 填充 ) + 8 + 2 + 2 ( 填充 ) = 1+3(填充)+8+2+2(填充)= 1+3(填充)+8+2+2(填充)=16字节。
4. 结构体内存对齐总结
结构体内存对齐的核心是空间换效率,理解对齐规则有助于优化内存布局。通过合理排列成员、利用编译指令和工具验证,可以在内存占用和性能之间找到平衡。
5. 内存中的字节序
5.1 基本概念
-
大端模式(Big-Endian)
数据的高有效字节序存储在内存的低地址,低有效字节存储在高地址。
类似于人类阅读顺序(从左到右是高位到低位)
示例:整数
0x11223344
(4字节)在内存中的存储: -
小端模式(Little-Endian)
数据的低有效字节存储在内存低地址,高有效字节存储在高地址。
类似于“反序”存储。
示例:整数
0x11223344
(4字节)在内存中的存储:
5.2 验证存储模式
- 使用
char*
指针变量接收int
类型变量,并解引用访问。
int main()
{
int i = 0x11223344;
char* c = &i;
if (*c == 0x11)
{
printf("大端存储\n");
}
else if (*c == 0x44)
{
printf("小端存储\n");
}
return 0;
}
- 使用联合体自定义类型。
union Un
{
int i;
char c;
};
int main()
{
union Un u;
u.i = 1;
if (u.c == 1)
{
printf("小端存储\n");
}
else
{
printf("大端存储\n");
}
return 0;
}
[!CAUTION]
字节序由处理器架构决定,而非编译器。编译器生产的代码会遵循目标平台的字节序。