登录
|
注册会员
开启辅助访问
设为首页
收藏本站
扫一扫关注官方微信
论坛
BBS
M币充值
M currency prepaid phone
M币获取
附件中心
搜索
search
全新论坛MCU智学网上线,欢迎访问新论坛!稀缺资源、技术干货、参考设计、原厂资料尽在MCU智学网
MCU资讯论坛
»
论坛
›
单片机开发论坛
›
C语言论坛
›
本人工作10年总结文档——《C语言字节对齐》
更新自动建库工具PCB Footprint Expert 2023.13 Pro / Library Expert 破解版
本人工作10年总结文档——《C语言字节对齐》
[复制链接]
3744
2
ifree***
新手上路
发表于 2015-6-22 16:21:17
|
查看全部
|
阅读模式
本文包含原理图、PCB、源代码、封装库、中英文PDF等资源
您需要
登录
才可以下载或查看,没有帐号?
注册会员
x
全文请见附件
C语言字节对齐.pdf
(262.59 KB, 下载次数: 13)
2015-6-22 16:14 上传
点击文件名下载附件
在word里写好了,在
论坛
里不好排版,请下载附件观看全文
全文章节:
字节对齐的由来
字节对齐规则
非字节对齐访问
非字节对齐的方法
非字节对齐类型的字节对齐规则
非字节对齐的影响
字节对齐的由来
程序在运行时会将数据临时存放在内存中,芯片内核需要对这些数据进行计算,不断的
读取内存以获得数据,并将计算结果写入内存。计算机体系经过若干年的发展,最终确定了
以8bits 作为其基本的存储单元——byte(字节),这是每个地址所对应的最小访问单元,
在C 语言中对应一个char 型的变量。
下图为芯片内核访问内存的示意图。芯片内核通过控制总线控制内存的动作,通过地址
总线告知内存地址,数据总线上出现交互的数据。
图1 访问内存示意图
假设上图是8 位机的示意图,那么数据总线的宽度是8bits,由8 根数据线组成,这样
芯片内核与内存之间一次就可以同时交换8 个bits 的数据,正好是一个字节。图中右侧的
每个小格子代表一个存储地址,对应一个字节。
下面通过一段C 语言代码来具体看看芯片内核与内存之间的数据交互过程。
char data[2];
data[0] = 2;
data[1] = data[0] + 1;
第一行代码定义了2 个字节的数组data。假设data 数组被编译到地址0x100,那么
data[0]这个字节就被存储在地址为0x100 的内存空间,data[1]这个字节就被存储在地址为
0x101 的内存空间。
第二行对应的硬件动作是将数据2 存入到data[0]中,也就是将数据2 存入到内存中的
0x100 地址,执行这条语句时,芯片内核对控制总线、地址总线和数据总线进行操作,控制
总线上出现写信号,地址总线上出现数据0x100,数据总线上出现数据0x02。此时内存就
知道需要将数据2 写入到地址0x100 中,完成一次写操作。
第三行先读出data[0]中的数据,芯片内核将控制总线置为读信号,将地址总线置为
0x100,此时,内存就会从其内部取出0x100 地址中的数据,也就是数据2,2 将出现在数
据总线上,此时芯片内核就会通过数据总线读取到data[0]中的数据了。接下来芯片内核计
算2+1=3,需要将数字3 写入到data[1]中,芯片内核将控制总线置为写信号,将地址总线
置为0x101,将数据总线置为3,内存接收到这些信号后,就会将数据3 存入到其内部0x101
地址中,完成本次操作。
内存内部细节
数据总线
地址总线
芯片内核 内存 0x100 2 3
控制总线
从上述介绍的过程可以看出,芯片内核与存储芯片之间每次操作可以传递1 个字节的
数据,如果要传递多个字节的数据就需要重复这个过程,这受限于数据总线的宽度。
计算机技术在不断的发展,在8bits 数据总线之后又相继出现了16bits、32bits 乃至
64bits 数据总线,它们分别对应于我们所谓的8 位机、16 位机、32 位机以及64 位机。对
于16 位机一次可以交互2 个字节的数据,32 位机一次可以交互4 个字节的数据,64 位机
一次可以交互8 个字节的数据,可以看出总线的带宽增加了,速度成倍提高。
以32 位机为例,我们在访问0 地址时,可以一次访问4 个字节的数据,这4 个字节的
数据占用了4 个内存地址,也就是说访问0 地址时同时可以访问0、1、2、3 这4 个地址,
访问4 地址时可以同时访问4、5、6、7 这4 个地址。我们不难得出这样的结论:在地址总
线上只要出一个地址,就可以连同访问这个地址及其后面的3 个地址中的数据,这4 个地
址正好可以组成一个32bits 的数据,通过访问数据总线一次即可获得,而对这个地址的要
求就是:需要4 字节对齐(对于64 位机则需要8 字节对齐)。在芯片设计时遵循了这个要
求,地址总线上只需要出现0、4、8……这样4 的整数倍的地址就可以同时访问连续4 个字
节的内存空间,这就是字节对齐的根源——是由硬件决定的!为了配合硬件的4 字节对齐
访问,软件的编译器链接器也对软件做了限制,需要4 字节对齐访问。
有关计算机的设计五花八门,上述有关控制总线、地址总线、数据总线的介绍只是原理性的介绍,不同芯片在具体实现时
会有所不同。
字节对齐规则
我们在写代码时一般并不会指定变量存放在内存中的地址,这是由编译器链接器决定
的,而编译器链接器则遵循了4 字节对齐的原则,以32 位机为例,其规则是1 字节长度的
变量可以被编译链接到任何地址,2 字节长度类型的变量被编译链接到2 的整数倍的地址,
4 字节长度类型的变量被编译链接到4 的整数倍的地址。因此,取signed/unsigned char
类型变量的地址,它可以是任意地址。取signed/unsigned short int 类型变量的地址,它一
定是2 的整数倍。取signed/unsigned int,signed/unsigned long 类型变量的地址,它一定
是4 的整数倍。
C 语言的结构体类型由多种基本类型组成,比较利于讨论字节对齐的问题,下面我们将
以结构体为例讲解字节对齐规则。以下例子除特殊说明外,均是在X86 32 位CPU,VC2010
环境下测试。
例1:
typedef struct example1
{
char a;
}EXAMPLE1;
结构体EXAMPLE1 比较简单,它其实就是一个char 型,它的长度sizeof(EXAMPLE1)
为1。
例2:
typedef struct example2
{
char a;
short b;
}EXAMPLE2;
结构体EXAMPLE2 中包含了2 个变量,其中char 型a 的长度为1,short 型b 的长度
为2,但结构体EXAMPLE2 的整体长度sizeof(EXAMPLE2)却为4,而不是1+2=3,这种
现象就是字节对齐造成的。
为了方便观察结构体中变量相对结构体头的偏移地址,我们定义如下的宏:
#define OFFSET(s, e) ((unsigned int)(&((s*)0)->e))
其中s 为结构体类型,e 为结构体中的变量,OFFSET 返回的就是结构体中的变量e
相对于结构体s 的偏移地址。通过该结构就可以看出结构体在内存中的分布。
求得结构体EXAMPLE2 的数据如下:
sizeof(EXAMPLE2) 4
OFFSET(EXAMPLE2, a) 0
OFFSET(EXAMPLE2, b) 2
画出结构体EXAMPLE2 在内存中分布如下:
ab
b
其中每个格子代表一个字节,a 和b 之间灰色背景的格子是编译器为了字节对齐而保留
的一个字节空间。为什么会保留一个字节的空间呢,这是因为结构体的对齐长度必须是其内
部变量类型中最长的对齐长度,也就是说存放结构体的起始地址必须是其内部变量类型中最
长的对齐长度的整数倍。结构体EXAMPLE2 中变量a 的对齐长度是1,变量b 的对齐长度
是2,因此EXAMPLE2 存放的地址必须是2 的整数倍。变量a 可以存放在任何地址,因此
存放在EXAMPLE2 开始的第一个字节,这个字节所在的地址是2 的整数倍,接下来的字节
(灰色)所在的地址不是2 的整数倍,而变量b 又只能存放在2 的整数倍地址,因此a 和b
之间只好空出1 个字节,这就使结构体EXAMPLE2 的长度变为4 了。
例3:
typedef struct example3
{
char a;
short b;
int c;
}EXAMPLE3;
在结构体EXAMPLE2 的基础上再增加一个int 变量c 构造成结构体EXAMPLE3,按照
例2 中介绍的方法分析一下结构体EXAMPLE3 的长度。
EXAMPLE3 中最长对齐长度的变量是c,4 个字节,因此EXAMPLE3 开始的地址必须
是4 的整数倍。变量a 是1 个字节,存放在EXAMPLE3 开始的第一个字节。变量b 是2
个字节,需要在a 之后空出1 个字节,才能存放在2 字节对齐的地址。变量c 是4 个字节,
需要存放在4 字节对齐的地址,前面的变量a、保留字节和变量b 之后已经是4 字节对齐的
地址了,因此变量c 可以直接存放在变量b 之后。
按照上面的分析,我们可以画出EXAMPLE3 在内存中的分布示意图:
a b b
c c c c
可以看到EXAMPLE3 占有8 个字节。我们再使用sizeof 和OFFSET 计算EXAMPLE3
的数据进行验证,如下:
sizeof(EXAMPLE3) 8
OFFSET(EXAMPLE3, a) 0
OFFSET(EXAMPLE3, b) 2
OFFSET(EXAMPLE3, c) 4
例4:
typedef struct example4
{
char a;
char b;
short c;
int d;
}EXAMPLE4;
在结构体EXAMPLE3 的基础上再增加一个char 的变量构造成结构体EXAMPLE4,
EXAMPLE4 比EXAMPLE3 多了一个char 型变量,那么EXAMPLE4 是否会比EXAMPLE3
长1 个字节?
EXAMPLE4 中最长的对齐长度的变量是d,4 个字节,因此EXAMPLE4 开始的地址必
须是4 的整数倍。变量a 是1 个字节,存放在EXAMPLE4 开始的第一个字节。变量b 是1
个字节,对字节对齐没有要求,直接存放在a 后面。变量c 是2 个字节,在a、b 之后已经
是2 字节对齐的地址了,因此c 可以直接存放在b 之后,对齐到2 个字节。变量d 是4 个
字节,在a、b、c 之后已经是4 字节对齐的地址了,因此d 可以直接存放在c 之后,对齐
到4 个字节。
按照上面的分析,我们可以画出EXAMPLE4 在内存中的分布示意图:
a b c c
d d d d
可以看到EXAMPLE4 虽然比EXAMPLE3 多了一个变量,但与EXAMPLE3 一样同样
占有8 个字节。我们再使用sizeof 和OFFSET 计算EXAMPLE3 的数据进行验证,如下:
sizeof(EXAMPLE4) 8
OFFSET(EXAMPLE4, a) 0
OFFSET(EXAMPLE4, b) 1
OFFSET(EXAMPLE4, c) 2
OFFSET(EXAMPLE4, d) 4
例5:
typedef struct example5
{
short a;
char b;
}EXAMPLE5;
再来看EXAMPLE5,按照上面介绍的规则你是否会认为它的长度是3?
EXAMPLE5 在内存中分布示意图如下:
a a
b
结构体不但要保证其存放的地址需要对齐到其内部变量类型中最长对齐长度的长度的
整数倍,其长度也要保证是其内部变量类型中最长的对齐长度的整数倍。EXAMPLE5 中最
长的对齐长度变量是a,2 个字节,因此它也必须是2 字节的整数倍,所以在b 之后需要填
充1 个字节。因此sizeof(EXAMPLE5)为4。
例6:
typedef struct example6
{
char a;
int b;
short c;
}EXAMPLE6;
按照前面介绍的方法可以得知EXAMPLE6 的长度是12,在内存中分布示意图如下:
ab
b b b
c c
EXAMPLE6 的数据如下:
sizeof(EXAMPLE6) 12
OFFSET(EXAMPLE6, a) 0
OFFSET(EXAMPLE6, b) 4
OFFSET(EXAMPLE6, c) 8
例7:
typedef struct example7_1
{
char a;
int b;
char c;
}EXAMPLE7_1;
typedef struct example7_2
{
short a;
EXAMPLE7_1 b;
char c;
}EXAMPLE7_2;
当一个结构体被包含在另外一个结构体中时,我们仍可以使用上面的方法进行分析。
先来看被包含的结构体EXAMPLE7_1,它按照4 字节对齐,长度是12,它的内存分
布示意图如下:
ab
b b b
c
对于结构体EXAMPLE7_2,short 型为2 字节对齐,EXAMPLE7_1 型被看做一个整体,
为4 字节对齐,char 型为1 字节对齐,因此结构体EXAMPLE7_2 也需要4 字节对齐,可
以得出EXAMPLE7_2 的内存分布示意图如下:
a a
b.a
b.b b.b b.b b.b
b.c
c
由于EXAMPLE7_1 作为一个整体存在,其内部的char 型变量b.a 并不会直接接在变
量a 后面,char 型变量c 也不会直接接在EXAMPLE7_2 内部的b.c 之后。由于EXAMPLE7_2
是4 字节对齐的,因此变量c 之后需要保留3 个字节对齐到4 字节。
例8:
typedef struct example8_1
{
char a;
short b;
}EXAMPLE8_1;
typedef struct example8_2
{
char a;
EXAMPLE8_1 b;
char c;
}EXAMPLE8_2;
再来看一下例8 这个例子,EXAMPLE8_1 按照2 字节对齐,长度是4,它的内存分布
示意图如下:
ab
b
对于结构体EXAMPLE8_2,char 型为1 字节对齐,EXAMPLE8_1 型为2 字节对齐,
因此结构体EXAMPLE8_2 也需要2 字节对齐。在EXAMPLE8_2 中将EXAMPLE8_1 看做
一个整体,可以得出EXAMPLE8_2 的内存分布示意图如下:
a
b.a
b.b b.b
c
由于EXAMPLE8_1 作为一个整体存在,其内部的char 型变量b.a 并不会直接接在变
量a 后面。由于EXAMPLE8_2 是2 字节对齐的,因此变量c 之后需要保留1 个字节对齐
到2 字节。
上面我们了解了字节对齐的规则,是以32 位机为例的。8 位机中硬件一次所能操作的
最大长度是1 个字节,多个字节的操作也是由单个字节组成的,因此8 位机没有字节对齐
的概念。例如过去所广泛使用的8 位
单片机
,它的int 型是2 个字节,long 型是4 个字节,
但受硬件限制在硬件操作时都是按字节操作的。
理解了这一点,下面的结构体在8 位机上的结果也就不意外了:
C语言
相关帖子
从零开始学单片机C语言,很全的资料
C语言编程宝典
单片机C语言程序设计实训100例-基于8051 Proteus仿真
C语言经典程序100题(完整版)
【众拳】STM8入门视频教程资料分享
ATmega16运行速度
上课了讲解C语言之一维数组
求助
国产单片机推荐:无锡矽杰微电子XC8P8600
51单片机STC8H8K64U通过RA8889/RA8876如何控制彩屏(I2C源码下载)
举报
回复
2 个评论
ca***
新手上路
发表于 2015-6-27 21:42:42
|
显示全部楼层
学习的好资料!!
举报
回复
支持
反对
lyd***
新手上路
发表于 2016-6-6 15:57:30
|
显示全部楼层
非常感谢您的分享
举报
回复
支持
反对
返回列表
*
滑块验证:
高级模式
B
Color
Image
Link
Quote
Code
Smilies
您需要登录后才可以回帖
登录
|
注册会员
本版积分规则
发表回复
回帖后跳转到最后一页
回复
转播
评分
分享
打开支付宝扫一扫,最高立得1212元红包
搜索
本版
帖子
用户
热搜:
传感器
51串口程序
电子管放大器
夾式電表
夾式電流
Mentor论坛
打印机
版块推荐
百宝箱
My 布拉格
无边框Z9
Z9Max
Z9mini
nubia动态
问题 & 建议
资源分享
爱拍
同城会
牛仔生活
查看论坛所有版块>>
每日签到
论坛任务
摄影技巧
跳蚤市场
互助问答
论坛导读
申请内测
红包中心
每日摇一摇
活动中心
网站地图
官方旗舰店
图文热点
俄罗斯的6H23+6П43推挽电路机器欣赏
AP3215 8-150V 外围简单 宽输入 电压降压BU
产品描述 AP3215是 一系列外围电路简洁的宽输入电压降压BUCK 恒压恒流驱动器 ,适用
VK1056Q QFN246是一个点阵式存储映射的LCD
产品品牌:永嘉微电/VINKA 产品型号:VK1056Q 封装形式:QFN24L 概述 VK1056Q是
超高清图解示波器使用方法
3线串行接口/工作电压工作电压 2.4-5.2V/VK
产品品牌:永嘉微电/VINKA 产品型号:VK1056B/C 封装形式:SOP/SSOP24 概述 VK1
更多
精华推荐
俄罗斯的6H23+6П43推挽电路机器欣赏
AP3215 8-150V 外围简单 宽输入 电压降压BU
VK1056Q QFN246是一个点阵式存储映射的LCD
一个名为伯努瓦(Gislain)的人,用了3年时
AP2121太阳能草坪灯驱动芯片
超高清图解示波器使用方法
3线串行接口/工作电压工作电压 2.4-5.2V/VK
单片机驱动彩屏最简方案:单片机_RA8889最
更多
社区学堂
俄罗斯的6H23+6П43推挽电路机器欣赏
AP3215 8-150V 外围简单 宽输入 电压降
俄罗斯的6H23+6П43推挽电路机器欣赏
AP3215 8-150V 外围简单 宽输入 电压降压BU
产品描述 AP3215是 一系列外围电路简洁的宽输入电压降压BUCK 恒压恒流驱动器 ,适用
VK1056Q QFN246是一个点阵式存储映射的LCD
产品品牌:永嘉微电/VINKA 产品型号:VK1056Q 封装形式:QFN24L 概述 VK1056Q是
更多
客服中心
QQ:187196467
服务时间:周一至周日 8:30-20:30
在线客服
客服微博
产品咨询
售后中心
关注我们
关于我们
关于我们
友情链接
联系我们
帮助中心
网友中心
购买须知
支付方式
服务支持
资源下载
售后服务
定制流程
关注我们
官方微博
官方空间
官方微信
QQ:187196467
周一到周日 8:30-22:00 (全年无休)
7 x 24小时在线客服
手机版
Powered by
MCUZX!
X3.4 © 2008-2015
MCU资讯论坛
版权所有
京ICP备18035221号-2
客服QQ: 187196467
技术支持:
MCU资讯论坛
|
网站地图
快速回复
返回顶部
返回列表