Bootstrap

【C】结构体内存对齐和大小端存储

1. 基本数据类型

C语言中基本数据类型包括charshortintlonglong longfloatdouble等,都是可以直接使用的,在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. 结构体的内存对齐

  • 既然结构体是由一个或多个数据类型构成的集合体,那么所占用的内存空间应该和基本数据类型创建的变量不一样,如上述结构体内包含charintdouble三种基本数据类型,把这三种数据类型所占大小相加得到 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 对齐规则

  1. 成员对齐

    第一个成员变量从偏移量**0位置开始占用内存空间,其余每个成员的起始地址必须是其对齐数**的整数倍。

    • 对齐数 = min(成员类型的大小, 编译器默认对齐数)
    • 默认对齐数通常由编译器决定,可通过#pragma pack(n)修改(此时对齐数为min(成员类型大小, n))。
  2. 结构体总大小

    结构体的总大小必须是其所有成员中最大对齐数的整数倍。

    • 若由嵌套结构体,最大对齐数可能包含嵌套结构体的最大对齐数。
  3. 结构体作为成员时的对齐

    当结构体作为其他结构体的成员时,其起始地址必须是自身最大对齐数的整数倍。

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 验证存储模式

  1. 使用char*指针变量接收int类型变量,并解引用访问。
int main()
{
	int i = 0x11223344;
	char* c = &i;
	if (*c == 0x11)
	{
		printf("大端存储\n");
	}
	else if (*c == 0x44)
	{
		printf("小端存储\n");
	}
	return 0;
}
  1. 使用联合体自定义类型。
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]

字节序由处理器架构决定,而非编译器。编译器生产的代码会遵循目标平台的字节序。

;