首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >结构体内存对齐(计算结构体的大小)

结构体内存对齐(计算结构体的大小)

作者头像
我不是呆头
发布2025-12-20 10:17:21
发布2025-12-20 10:17:21
2600
举报
文章被收录于专栏:学习学习

前言

🚀 欢迎来到《C语言:从入门到"入土"》! 这里是 从"Hello World"到"指针渡劫" 的硬核修炼场,也是你从语法萌新成长为能徒手调试内核的终极试炼场。

🔍 专栏特色

  • 生死簿式教学:把内存泄漏比作"阳寿未尽",段错误当作"渡劫失败"
  • 地狱级实战:每个案例都是血泪教训(包括我自己的)
  • 系统化深入:从语法基础到Linux系统编程全贯通

💡 生存指南: 1️⃣ 先写个野生指针版(体验下随机崩溃的刺激) 2️⃣ 学习规范写法(掌握内存管理法则) 3️⃣ 挑战内核级开发(真正"入土"为安)

📌 C程序员觉悟: “要么你征服指针和内存,要么它们送你提前入土为安!”

(正文开始👇)

一、结构体内存对齐原则

我们知道,整型变量有自己的大小,浮点型变量有自己的大小,数组也有自己的大小,那么结构体有没有自己的大小呢? 回答是肯定的,结构体也有自己的大小,但是结构体的大小并不是简单地将每个结构体成员的大小相加就能得到。

结构体的大小计算遵循结构体的对齐规则:

  1. 第一个成员在与结构体变量偏移量为0的地址处。(即结构体的首地址处,即对齐到0处) 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
  2. 结构体的总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
  3. 如果嵌套了结构体,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

对齐数 = 该结构体成员变量自身的大小与编译器默认的一个对齐数的较小值。

注: VS中的默认对齐数为8,不是所有编译器都有默认对齐数,当编译器没有默认对齐数的时候,成员变量的大小就是该成员的对齐数。

二、计算结构体大小三步曲

以此代码为例

代码语言:javascript
复制
struct S
{
	double d;
	char c;
	int i;
};

第一步:找出每个成员变量的大小将其与编译器的默认对齐数相比较,取其较小值为该成员变量的对齐数。

注:我使用的是VS编译器,故默认对齐数为8。

第二步:根据每个成员对应的对齐数画出它们在内存中的相对位置。

第三步:通过最大对齐数决定最终该结构体的大小。 通过图我们可以知道,绿色部分(double d成员占用)+红色部分(char c成员占用)+紫色部分(int i成员占用)+红色与紫色之间的白色部分(浪费掉了)总共占用了16个字节的内存空间。 我们需要将它们总共占用的内存空间(16)与结构体成员的最大对齐数(8)相比较,结构体的总大小为最大对齐数的整数倍,此时16正好是8的整数倍,所以该结构体在VS编译器下的大小就16个字节。即创建一个该类型的结构体变量,内存需为其开辟16个字节的内存空间。

注意:大多数情况下,成员变量已经占用的总字节个数并不一定正好为其成员变量中的最大对齐数的整数倍,这时我们需要将其扩大为最大对齐数的整数倍。

三、为什么存在内存对齐

1.平台原因(移植原因): 不是所有的硬件平台都能访问任意地址上的任意数据的;某些平台只能在某些地址处取得某些特定类型的数据,否则抛出硬件异常。 比如,当一个平台要取一个整型数据时只能在地址为4的倍数的位置取得,那么这时就需要内存对齐,否则无法访问到该整型数据。

2. 性能原因: 数据结构(尤其是栈)应该尽可能的在自然边界上对齐。原因在于,为了访问未对齐内存,处理器需要作两次内存访问;而对齐的内存访问仅需一次。

3.在画图时可能有博友会想,内存这么重要,在进行内存对齐的时候怎么还有内存被白白浪费掉呢? 现在看来,其实结构体的内存对齐是拿空间来换取时间的做法。

四、设计结构体时的技巧

其实在我们设计结构体的时候,如果结构体成员的顺序设计得合理的话,是可以避免不必要的内存消耗的。 两个结构体的成员变量相同,但是成员变量的顺序不同,可能就会出现结构体的大小不同的情况:

代码语言:javascript
复制
struct S1
{
	char a;
	char b;
	int c;
};//结构体1
struct S2
{
	char a;
	int c;
	char b;
};//结构体2

我们可以看到,结构体1和结构体2的成员变量一模一样,可是当我们按照内存对齐规则来计算两个结构体的大小的时候,会发现两个结构体的大小不一样,在VS编译器下第一个结构体大小为8,第二个结构体大小为12

可以见得,结构体成员变量的顺序不同,可能会造成内存不必要的损失。将占用空间小的成员尽量集中在一起,可以有效地避免内存不必要的浪费。

五、修改默认对其参数

要修改编译器的默认对齐数,我们需要借助于以下预处理命令:

代码语言:javascript
复制
#pragma pack()

如果在该预处理命令的括号内填上数字,那么默认对齐数将会被改为对应数字;如果只使用该预处理命令,不在括号内填写数字,那么会恢复为编译器默认的对齐数。

代码语言:javascript
复制
#include <stdio.h>

#pragma pack(4)//设置默认对齐数为4
struct S1
{
	char a;//1/4->1
	int b;//4/4->4
	char c;//1/4->1
};//12
#pragma pack()//取消设置的默认对齐数,还原为默认

#pragma pack(1)//设置默认对齐数为1
struct S2
{
	char a;//1/1->1
	int b;//4/1->1
	char c;//1/1->1
};//6
#pragma pack()//取消设置的默认对齐数,还原为默认

int main()
{
	printf("%d\n", sizeof(struct S1));//打印结果为12
	printf("%d\n", sizeof(struct S2));//打印结果为6
	return 0;
}

于是,当结构体的对齐方式不合适的时候,我们可以自己更改默认对齐数。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-08-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 一、结构体内存对齐原则
  • 二、计算结构体大小三步曲
  • 三、为什么存在内存对齐
  • 四、设计结构体时的技巧
  • 五、修改默认对其参数
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档