当前位置: 首页 > news >正文

【C语言】结构体内存布局解析——字节对齐

🦄个人主页:小米里的大麦-CSDN博客

🎏所属专栏:https://blog.csdn.net/huangcancan666/category_12718530.html

🎁代码托管:黄灿灿 (huang-cancan-xbc) - Gitee.com

⚙️操作环境:Visual Studio 2022

目录

一、引言

二、什么是字节对齐?

 三、字节对齐规则

1. 成员对齐规则:

2. 结构体整体对齐规则:

3. 编译器依赖性:

小结

四、示例分析

内存布局

示例一:简单结构体

示例二:复杂对齐规则

示例三:自定义对齐方式

思考:(自行尝试一下吧)

五、如何自定义对齐方式

使用#pragma pack

 使用__attribute__((packed))

六、总结

共勉


一、引言

在C语言中,结构体是一种用户定义的数据类型,一种非常有用的数据类型,它允许程序员将不同类型的数据组织在一起。结构体的大小并不是简单地等于所有成员变量大小之和,因为编译器会对结构体中的数据进行字节对齐(byte alignment),以优化访问速度。理解结构体在内存中的布局对于编写高效且可维护的代码至关重要。本文将探讨结构体内存布局的基本原理。

二、什么是字节对齐?

字节对齐是指数据在内存中的存储方式,以提高内存访问效率。大多数现代计算机系统在内存访问时,要求数据地址满足特定的对齐条件,否则可能会导致访问效率下降,甚至是硬件异常。

具体来说,数据的对齐要求是由其数据类型决定的。例如,4字节的整型变量通常要求其地址是4的倍数。结构体中的每个成员也必须满足其对齐要求。

对齐规则通常由编译器和处理器架构共同决定。常见的规则包括:

  • 自然对齐:数据类型应该按照它的自然对齐要求放置。例如,一个int类型(假设为4字节)应该放在4字节对齐的位置上。
  • 最大对齐:结构体或联合中的所有成员都将按照最大成员的对齐要求进行对齐。
  • 固定对齐:有时编译器会强制所有成员按某个固定的对齐值进行对齐。

 三、字节对齐规则

1. 成员对齐规则

  • 每个成员会根据其类型自动对齐到一个特定的字节边界。例如,char 类型通常不需要对齐,而 int 可能需要 4 字节对齐。
  • 如果一个成员的偏移量不是其大小的倍数,则会在前面填充空字节以满足对齐要求。也就是浪费空间(以空间换时间)
  • 内存地址是从0开始递增的,因此在内存中,每个字节都有一个唯一的地址,这些地址是从0开始编号的。(这非常重要!!!)

2. 结构体整体对齐规则

  • 结构体自身也有一个对齐要求,通常是最大成员的对齐要求。这意味着整个结构体的大小必须是这个对齐值的倍数。
  • 第一个成员在与结构体变量偏移量为0的地址处。
  • 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
  • 对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值
  • VS中默认的值为8
  • 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
  • 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

3. 编译器依赖性

  • 不同的编译器可能有不同的默认对齐规则,这些规则可以通过预处理器宏或命令行选项来更改。(以VS为例,通常vs是以8字节对齐)

4. 小结

  • 成员的排列顺序:结构体成员按照声明的顺序依次存放。
  • 对齐方式:每个成员按照自身类型的对齐要求对齐。
  • 地址编号:内存地址从0开始编号。
  • 结构体的总大小:结构体的总大小必须是其最宽基本类型成员大小的倍数(即结构体的对齐方式)。

四、示例分析

下面通过几个例子来具体说明结构体大小的计算过程。

首先,让我们定义一个简单的结构体S,它包含了三个成员:一个整型变量a、一个字符数组c以及一个双精度浮点数d

struct S {int a;char c[5];double d;
};
这个结构体中包含的不同数据类型需要不同数量的字节来存储:int a: 假设在32位系统上,int通常占用4个字节。
char c[5]: 每个char占用1个字节,加上数组长度为5,因此占用5个字节。
double d: 在大多数系统上,double占用8个字节。

内存布局

当我们在程序中声明一个S类型的变量时,例如:

struct S s;

编译器会为这个结构体分配连续的内存空间,并按照成员出现的顺序依次存放它们。

  • 整型变量a:占据前4个字节。
  • 字符数组c:紧接着a后面,占据接下来的5个字节。
  • 双精度浮点数d:位于字符数组之后,占据了剩余的8个字节。

这意味着整个结构体S在内存中所占的空间为4 + 5 + 8 = 17个字节。(以8字节对齐,从0开始,最后的一个字节处于 ‘地址16’ 的位置,16刚好是8的倍数,不用增加,大小刚刚好)

请注意,由于不同的编译器可能有不同的内存对齐策略,因此实际的内存布局可能会有所不同。一些编译器会对结构体成员进行填充,以确保某些类型的对齐要求被满足。这种对齐可以帮助提高数据访问的速度,但也可能导致结构体的实际大小大于简单计算得出的大小。 

示例一:简单结构体

struct Example1 {char a;int b;short c;
};
我们逐一分析这个结构体的内存布局:char a,大小为1字节,对齐要求为1字节。
int b,大小为4字节,对齐要求为4字节。为了满足对齐要求,在char a之后需要3字节的填充。
short c,大小为2字节,对齐要求为2字节。int b之后无需填充。

内存布局如下:

| a(1) | 填充(3) | b(4) | c(2) | 填充(2) |

结构体的总大小应是最大对齐要求的倍数,这里是4的倍数,所以最终大小为12字节。

示意图(这样表示只是便于观察,其实并不准确,大概知道内存中是怎样存储即可!!)

| a | - | - | - | b | b | b | b | c | c | - | - |    ————存储0   1   2   3   4   5   6   7   8   9  10   11     ————地址编号

示例二:复杂对齐规则

struct Example2 {double a;char b;int c;
};
内存布局分析:double a,大小为8字节,对齐要求为8字节。
char b,大小为1字节,对齐要求为1字节。double a之后需要7字节的填充。
int c,大小为4字节,对齐要求为4字节。char b之后需要3字节的填充。

内存布局如下:

| a(8) | b(1) | 填充(7) | c(4) | 填充(4) |

结构体的总大小应是最大对齐要求的倍数,这里是8的倍数,所以最终大小为24字节。

示意图如下:

| a | a | a | a | a | a | a | a | b | - | - | - | - | - | - | - | c | c | c | c | - | - | - | - |

示例三:自定义对齐方式

#pragma pack(1)
struct Example3 {char a;int b;short c;
};
#pragma pack()

通过设置#pragma pack(1),我们取消了默认的对齐要求,结构体大小变为:

| a(1) | b(4) | c(2) |

总大小为7字节。 

 示意图如下:

| a | b | b | b | b | c | c |

思考:(自行尝试一下吧)

这里是原代码,尝试一下吧~
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>struct S1
{char c1;int i;char c2;
}S1;
struct S2
{char c1;char c2;int i;
}S2;int main()
{printf("%d\n", sizeof(S1));printf("%d\n", sizeof(S2));return 0;
}

五、如何自定义对齐方式

在某些情况下,我们可以使用#pragma pack指令或__attribute__((packed))来改变默认的对齐方式。

使用#pragma pack

#pragma pack(1)
struct Packed {char a;int b;short c;
};
#pragma pack()

通过设置#pragma pack(1),我们取消了默认的对齐要求,结构体大小变为:

| a(1) | b(4) | c(2) |

总大小为7字节。

 使用__attribute__((packed))

struct Packed {char a;int b;short c;
} __attribute__((packed));

#pragma pack(1)效果相同,总大小也是7字节。

六、总结

理解结构体的内存对齐规则对于优化内存使用和提高程序性能非常重要。以下是一些关键点:

  • 结构体成员按声明顺序排列,满足自身对齐要求。
  • 结构体总大小是其最大对齐要求的倍数。
  • 可以使用#pragma pack__attribute__((packed))自定义对齐方式。
  • 地址编号:内存地址从0开始编号。
  • 对齐规则:数据类型应该按照其大小的倍数对齐。
  • 填充:为了满足对齐要求,编译器可能会在结构体中插入空隙(填充)。

通过这些规则和技巧,我们可以更好地设计和使用C语言中的结构体。希望本文对您理解结构体大小计算和字节对齐有所帮助。

共勉

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • C# 工厂方法模式
  • 嵌入式linux相机 图像处理模块
  • 【学习方法】高效学习因素 ① ( 开始学习 | 高效学习因素五大因素 | 高效学习公式 - 学习效果 = 时间 x 注意力 x 精力 x 目标 x 策略 )
  • 解析Java中1000个常用类:HashSet类,你学会了吗?
  • 【保姆级系列:锐捷模拟器的下载安装使用全套教程】
  • Pr2024苹果(mac)版剪辑软件安装下载(附下载链接)
  • 计算机毕业设计Hadoop+Hive专利分析可视化 面向专利的大数据管理系统 专利爬虫 专利数据分析 大数据毕业设计 Spark
  • 基于切片法计算点云体积 双向最近点三维点排序
  • (计算机网络)物理层
  • 利用Dockerfile文件执行docker build自动构建镜像
  • 【java】单行注释(//)与多选注释(/* */)
  • 【iOS】APP仿写——天气预报
  • 文件解析漏洞集合
  • Python应用—简单邮件发送功能
  • 详解C/C++输入输出
  • 收藏网友的 源程序下载网
  • 【comparator, comparable】小总结
  • 【许晓笛】 EOS 智能合约案例解析(3)
  • CSS3 变换
  • HTTP 简介
  • Meteor的表单提交:Form
  • Next.js之基础概念(二)
  • Shell编程
  • Travix是如何部署应用程序到Kubernetes上的
  • yii2中session跨域名的问题
  • 阿里中间件开源组件:Sentinel 0.2.0正式发布
  • 面试总结JavaScript篇
  • 浅析微信支付:申请退款、退款回调接口、查询退款
  • gunicorn工作原理
  • #NOIP 2014# day.1 T3 飞扬的小鸟 bird
  • (13)[Xamarin.Android] 不同分辨率下的图片使用概论
  • (STM32笔记)九、RCC时钟树与时钟 第一部分
  • (二)JAVA使用POI操作excel
  • (机器学习-深度学习快速入门)第三章机器学习-第二节:机器学习模型之线性回归
  • (离散数学)逻辑连接词
  • (面试必看!)锁策略
  • (学习日记)2024.04.04:UCOSIII第三十二节:计数信号量实验
  • (一)pytest自动化测试框架之生成测试报告(mac系统)
  • (原創) 如何優化ThinkPad X61開機速度? (NB) (ThinkPad) (X61) (OS) (Windows)
  • (转)菜鸟学数据库(三)——存储过程
  • (自用)网络编程
  • .desktop 桌面快捷_Linux桌面环境那么多,这几款优秀的任你选
  • .net core 依赖注入的基本用发
  • .NET 应用启用与禁用自动生成绑定重定向 (bindingRedirect),解决不同版本 dll 的依赖问题
  • .NET/C# 编译期能确定的字符串会在字符串暂存池中不会被 GC 垃圾回收掉
  • .NETCORE 开发登录接口MFA谷歌多因子身份验证
  • .Net程序帮助文档制作
  • .NET中的Event与Delegates,从Publisher到Subscriber的衔接!
  • .net中的Queue和Stack
  • @vue/cli脚手架
  • [ 常用工具篇 ] AntSword 蚁剑安装及使用详解
  • []Telit UC864E 拨号上网
  • [Android 13]Input系列--获取触摸窗口
  • [AX]AX2012 R2 出差申请和支出报告
  • [bzoj2957]楼房重建