zl程序教程

您现在的位置是:首页 >  其他

当前栏目

详细解读C语言结构体的内存对齐

2023-02-26 09:49:31 时间

前言

本文介绍结构体相关的偏移量、对齐数、对齐规则、内存对齐存在的原因、修改对齐数等知识点~

偏移量

在知道结构体如何对齐之前,需要知道什么是结构体的偏移量。

假设下面这张图是结构体的存储位置,第二列的数字标识代表着偏移量。

结构体的每个成员变量必须按照对齐规则放在规定的偏移量处。

对齐法

  1. 结构体第一个成员必须在偏移量为0处。
  2. 其他成员变量要从上到下在其对齐数的整数倍处。
  3. 若结构体内嵌套结构体,则该结构体成员的对齐数为自身成员的对齐数中的最大的那个。
  4. 结构体的总大小必须是所有成员变量的对齐数中最大的那个的整数倍。

这里引出了一个新名词:对齐数。若想了解对齐法,先要了解对齐数。

对齐数

每个成员变量都有相应的对齐数,其值等于该成员自身的大小编译器默认对齐数之间较小的那一个。

不同的编译器有不同的默认对齐数,例如VS环境的默认对齐数是8,而gcc环境下就没有规定好的默认对齐数。

以VS环境下举例,如何计算一个成员的对齐数,例如:

	struct MyStruct
	{
		char a;
		int b;
		double c;
	};

a的大小是1字节,默认对齐数是8,则a成员变量的对齐数就是1。

b的大小是4字节,默认对齐数是8,则b成员变量的对齐数就是4。

c的大小是8字节,默认对齐数是8,则c成员变量的对齐数就是8。

对齐法详解

下面我们对对齐方法进行详细介绍。

1,结构体的第一个成员变量必须在偏移量为0处。

这句话很容易理解,即结构体的第一个成员变量,无论其对齐数是多少,都要从偏移量为0的地方开始存放,大小是几就存放几个字节。

例如:创建这么一个结构体

	struct MyStruct
	{
		int a;
		char b;
		int c;
	};

第一个成员变量是int类型的a,占4个字节,因此应该像这样存放:

在这里插入图片描述

2,其他成员变量要从上到下在对齐数的整数倍处。

	struct MyStruct
	{
		int a;
		char b;
		int c;
	};

b的对齐数是1和8之间较小的,是1。因此它要在1的倍数处存放。

c的对齐数是4和8之间较小的,是4。因此它要在4的倍数处存放。

因此他们应该这样存放:

在这里插入图片描述

由图可知,从0~11一共占用12个字节,则该结构体的大小就是12个字节。

3,若结构体内嵌套结构体,则该结构体成员的对齐数为自身成员的对齐数中的最大的那个。

还是以这个结构体为例:

	struct MyStruct
	{
		int a;//4<8     4
		char b;//1<8    1
		int c;//4<8     4
	};

该结构体自身成员中对齐数最大的是4,因此,当该结构体变成一个成员变量进入别的结构体时,该结构体的对齐数就是4,它应该被放在偏移量为4的倍数处。占用大小12个字节。

4,结构体的总大小必须是所有成员变量的对齐数中最大的那个的整数倍。

	struct MyStruct
	{
		int a;//4<8     4
		char b;//1<8    1
		int c;//4<8     4
	};

如图,在三个成员变量中最大的对齐数是4,则结构体的总大小就是4的倍数。

而我们刚才算出来的12刚好是4的倍数,因此这个结构体的大小就是12字节。

存在的原因

既然内存对齐这么麻烦,这么浪费空间,那么为什么要创造出这么个东西呢?

现在广为流传的一共有两个原因:

一、移植原因。有些硬件平台只能读取特殊的偏移量处的数据,如果不按照对齐规则存放,会导致程序的移植性低。

二、性能原因。32位的机器一次读取的数据大小是四个字节,假设有一个int型的变量a,占用了2,3,4,5这四个偏移量的内存,那么计算机读取a的时候需要把从0~7的数据全部读取一遍,要读取两次。而如果把a存放在4,5,6,7这四个偏移量处,那么计算机只需要读取4,5,6,7四个字节就可以成功地获得a。

总的来说,内存对齐是一种拿空间换取时间的行为。

都不想放过

如果你既想按照内存规则的方式来,还想让内存占用达到最小,就应该让占用内存小的成员变量尽可能靠在一起,像这样:

struct S1
{
	char c1;
	int i;
	char c2;
};

struct S2
{
	char c1;
	char c2;
	int i;
};

这两种类型,只有成员变量的顺序不同,但结构体占用的总大小也有所不同。

第一种:占用12个字节

第二种:占用8个字节

修改对齐数

你可以使用以下两条预处理指令来修改对齐数

1,#pragma pack(8)//设置默认对齐数为8

2,#pragma pack()//取消设置的默认对齐数,还原为默认

你想变强吗

想变强就来做题把,以下几个结构体,你能算出他们的大小吗?

练习1

struct S1
{
	char c1;
	int i;
	char c2;
};

练习2

struct S2
{
	char c1;
	char c2;
	int i;
};

练习3

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

练习4

struct S3
{
	double d;
	char c;
	int i;
};
struct S4
{
	char c1;
	struct S3 s3;
	double d;
};
int main()
{
	printf("%d\n", sizeof(struct S4));
	return 0;
}

感谢您的阅读与耐心~