没有合适的资源?快使用搜索试试~ 我知道了~
资源推荐
资源详情
资源评论
C 语言对齐问题,含结构体、栈内存以及位域
对齐
引言
考虑下面的结构体定义:
假设这个结构体的成员在内存中是紧凑排列的,且 c1 的起始地址是 0,则 s 的地址就是 1,
c2 的地址是 3,i 的地址是 4。
现在,我们编写一个简单的程序:
运行后输出:
为什么会这样?这就是字节对齐导致的问题。
本文在参考诸多资料的基础上,详细介绍常见的字节对齐问题。因成文较早,资料来源大多
已不可考,敬请谅解。
一,什么是字节对齐
现代计算机中,内存空间按照字节划分,理论上可以从任何起始地址访问任意类型的变量。
但实际中在访问特定类型变量时经常在特定的内存地址访问,这就需要各种类型数据按照一
定的规则在空间上排列,而不是顺序一个接一个地存放,这就是对齐。
二,对齐的原因和作用
不同硬件平台对存储空间的处理上存在很大的不同。某些平台对特定类型的数据只能从特定
地址开始存取,而不允许其在内存中任意存放。例如 Motorola 68000 处理器不允许 16 位的
字存放在其地址,否则会触发异常,因此在这种架构下编程必须保证字节对齐。
但最常见的情况是,如果不按照平台要求对数据存放进行对齐,会带来存取效率上的损失。
比如 32 位的 Intel 处理器通过总线访问(包括读和写)内存数据。每个总线周期从偶地址开始
访问 32 位内存数据,内存数据以字节为单位存放。如果一个 32 位的数据没有存放在 4 字
节整除的内存地址处,那么处理器就需要 2 个总线周期对其进行访问,显然访问效率下降
很多。
因此,通过合理的内存对齐可以提高访问效率。为使 CPU 能够对数据进行快速访问,数据
的起始地址应具有“对齐”特性。比如 4 字节数据的起始地址应位于 4 字节边界上,即起始地
址能够被 4 整除。
此外,合理利用字节对齐还可以有效地节省存储空间。但要注意,在 32 位机中使用 1 字节
或 2 字节对齐,反而会降低变量访问速度。因此需要考虑处理器类型。还应考虑编译器的
类型。在 VC/C++和 GNU GCC 中都是默认是 4 字节对齐。
三,对齐的分类和准则
主要基于 Intel X86 架构介绍结构体对齐和栈内存对齐,位域本质上为结构体类型。
对于 Intel X86 平台,每次分配内存应该是从 4 的整数倍地址开始分配,无论是对结构体变
量还是简单类型的变量。
3.1 结构体对齐
在 C 语言中,结构体是种复合数据类型,其构成元素既可以是基本数据类型(如 int、long、
float 等)的变量,也可以是一些复合数据类型(如数组、结构体、联合等)的数据单元。编译
器为结构体的每个成员按照其自然边界(alignment)分配空间。各成员按照它们被声明的顺序
在内存中顺序存储,第一个成员的地址和整个结构的地址相同。
字节对齐的问题主要就是针对结构体。
3.1.1 简单示例
先看个简单的例子(32 位,X86 处理器,GCC 编译器):
【例 1】设结构体如下定义:
已知 32 位机器上各数据类型的长度为:char 为 1 字节、short 为 2 字节、int 为 4 字节、long
为 4 字节、float 为 4 字节、double 为 8 字节。那么上面两个结构体大小如何呢?
结果是:sizeof(strcut A)值为 8;sizeof(struct B)的值却是 12。
结构体 A 中包含一个 4 字节的 int 数据,一个 1 字节 char 数据和一个 2 字节 short 数据;B
也一样。按理说 A 和 B 大小应该都是 7 字节。之所以出现上述结果,就是因为编译器要对
数据成员在空间上进行对齐。
3.1.2 对齐准则
先来看四个重要的基本概念:
(1)数据类型自身的对齐值:char 型数据自身对齐值为 1 字节,short 型数据为 2 字节,int/float
型为 4 字节,double 型为 8 字节。
(2)结构体或类的自身对齐值:其成员中自身对齐值最大的那个值。
(3)指定对齐值:#pragma pack (value)时的指定对齐值 value。
(4)数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中较小者,即有效对齐
值=min{自身对齐值,当前指定的 pack 值}。
基于上面这些值,就可以方便地讨论具体数据结构的成员和其自身的对齐方式。
其中,有效对齐值 N 是最终用来决定数据存放地址方式的值。有效对齐 N 表示“对齐在 N
上”,即该数据的“存放起始地址%N=0”。而数据结构中的数据变量都是按定义的先后顺序存
放。第一个数据变量的起始地址就是数据结构的起始地址。结构体的成员变量要对齐存放,
结构体本身也要根据自身的有效对齐值圆整(即结构体成员变量占用总长度为结构体有效对
齐值的整数倍)。
以此分析 3.1.1 节中的结构体 B:
假设 B 从地址空间 0x0000 开始存放,且指定对齐值默认为 4(4 字节对齐)。成员变量 b 的
自身对齐值是 1,比默认指定对齐值 4 小,所以其有效对齐值为 1,其存放地址 0x0000 符
合 0x0000%1=0。成员变量 a 自身对齐值为 4,所以有效对齐值也为 4,只能存放在起始地
址为 0x0004~0x0007 四个连续的字节空间中,符合 0x0004%4=0 且紧靠第一个变量。变
量 c 自身对齐值为 2,所以有效对齐值也是 2,可存放在 0x0008~0x0009 两个字节空间中,
符合 0x0008%2=0。所以从 0x0000~0x0009 存放的都是 B 内容。
再看数据结构 B 的自身对齐值为其变量中最大对齐值(这里是 b)所以就是 4,所以结构体的
有效对齐值也是 4。根据结构体圆整的要求,0x0000~0x0009=10 字节,(10+2)%4=0。所
以 0x0000A~0x000B 也为结构体 B 所占用。故 B 从 0x0000 到 0x000B 共有 12 个字节,
sizeof(struct B)=12。
之所以编译器在后面补充 2 个字节,是为了实现结构数组的存取效率。试想如果定义一个
结构 B 的数组,那么第一个结构起始地址是 0 没有问题,但是第二个结构呢?按照数组的定
义,数组中所有元素都紧挨着。如果我们不把结构体大小补充为 4 的整数倍,那么下一个
结构的起始地址将是 0x0000A,这显然不能满足结构的地址对齐。因此要把结构体补充成
有效对齐大小的整数倍。其实对于
char/short/int/float/double 等已有类型的自身对齐值也是基于数组考虑的,只是因为这些类
型的长度已知,所以他们的自身对齐值也就已知。
上面的概念非常便于理解,不过个人还是更喜欢下面的对齐准则。
结构体字节对齐的细节和具体编译器实现相关,但一般而言满足三个准则:
(1)结构体变量的首地址能够被其最宽基本类型成员的大小所整除;
(2)结构体每个成员相对结构体首地址的偏移量(offset)都是成员大小的整数倍,如有需要编
译器会在成员之间加上填充字节(internal adding);
(3)结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一
个成员之后加上填充字节{trailing padding}。
对于以上规则的说明如下:
(1)编译器在给结构体开辟空间时,首先找到结构体中最宽的基本数据类型,然后寻找内存
地址能被该基本数据类型所整除的位置,作为结构体的首地址。将这个最宽的基本数据类型
的大小作为上面介绍的对齐模数。
(2)为结构体的一个成员开辟空间之前,编译器首先检查预开辟空间的首地址相对于结构体
首地址的偏移是否是本成员大小的整数倍,若是,则存放本成员,反之,则在本成员和上一
剩余15页未读,继续阅读
资源评论
青少年编程作品集
- 粉丝: 4742
- 资源: 262
上传资源 快速赚钱
- 我的内容管理 展开
- 我的资源 快来上传第一个资源
- 我的收益 登录查看自己的收益
- 我的积分 登录查看自己的积分
- 我的C币 登录后查看C币余额
- 我的收藏
- 我的下载
- 下载帮助
最新资源
- 基于Kotlin语言的Android开发工具类集合源码
- 零延迟 DirectX 11 扩展实用程序.zip
- 基于Java的语音识别系统设计源码
- 基于Java和HTML的yang_home766个人主页设计源码
- 基于Java与前端技术的全国实时疫情信息网站设计源码
- 基于鸿蒙系统的HarmonyHttpClient设计源码,纯Java实现类似OkHttp的HttpNet框架与优雅的Retrofit注解解析
- 基于HTML和JavaScript的廖振宇图书馆前端设计源码
- 基于Java的Android开发工具集合源码
- 通过 DirectX 12 Hook (kiero) 实现通用 ImGui.zip
- 基于Java开发的YY网盘个人网盘设计源码
资源上传下载、课程学习等过程中有任何疑问或建议,欢迎提出宝贵意见哦~我们会及时处理!
点击此处反馈
安全验证
文档复制为VIP权益,开通VIP直接复制
信息提交成功