非字节对齐类型的字节对齐规则

我们可以使用“__packed”、“__attribute__((packed))”、“#pragma”等方式控制结构体的字节对齐,这些结构体的内部结构在定义时就已经确定了,当它们被包含在其它结构体内部时它们被当做一个整体来看待,其内部结构不受外面字节对齐控制方式的影响,但它们的对齐方式却受外面结构体的对齐方式控制,来看下面的例子:

#pragmapack(2)

typedef struct example26_1

{

char a;

int b;

char c;

}EXAMPLE26_1;

#pragma pack(1)

typedef struct example26_2

{

char a;

EXAMPLE26_1 b;

int c;

}EXAMPLE26_2;

#pragma pack()

typedef struct example26_3

{

char a;

EXAMPLE26_1 b;

int c;

}EXAMPLE26_3;

EXAMPLE26_1的内存分布示意图如下:

a

b

b

b

b

c

EXAMPLE26_1的数据如下:

sizeof(EXAMPLE26_1)

8

OFFSET(EXAMPLE26_1, a)

0

OFFSET(EXAMPLE26_1, b)

2

OFFSET(EXAMPLE26_1, c)

6

EXAMPLE26_1结构体按照2字节对齐,这个没什么好说的了,前面已经介绍过。

EXAMPLE26_2结构体按照1字节对齐,它里面的a、b、c都按照1字节对齐。其中b是一个按照2字节对齐的EXAMPLE26_1结构体,内部有2个填充的1字节,当b出现在要求1字节对齐的EXAMPLE26_2结构体中,b需要按照1字节对齐,注意,但其内部结构不能发生变化,那2个填充的1字节仍保留。

来看内存分布示意图:

a

b.a

b.b

b.b

b.b

b.b

b.c

c

c

c

c

EXAMPLE26_2的数据如下:

sizeof(EXAMPLE26_2)

13

OFFSET(EXAMPLE26_2, a)

0

OFFSET(EXAMPLE26_2, b.a)

1

OFFSET(EXAMPLE26_2, b.b)

3

OFFSET(EXAMPLE26_2, b.c)

7

OFFSET(EXAMPLE26_2, c)

9

EXAMPLE26_3结构体按照4字节对齐,EXAMPLE26_2结构体是按照2字节对齐的,因此b.a虽然是char型变量,但也需要对齐到2字节,在a之后需要保留一个填充字节。b.a与b.b之间保留的一个字节不是因为b.b需要对齐到4字节而保留的,而是EXAMPLE26_2结构体在定义时按2字节对齐而保留的。

来看内存分布示意图:

a

b.a

b.b

b.b

b.b

b.b

b.c

c

c

c

c

EXAMPLE26_3的数据如下:

sizeof(EXAMPLE26_3)

16

OFFSET(EXAMPLE26_3, a)

0

OFFSET(EXAMPLE26_3, b.a)

2

OFFSET(EXAMPLE26_3, b.b)

4

OFFSET(EXAMPLE26_3, b.c)

8

OFFSET(EXAMPLE26_3, c)

12

总结一下字节对齐的规则:

1.确定结构体中每种结构对齐的字节数,找出其中最大的字节对齐数N,求得结构体对齐规则的对齐数M,取M与N中的最小值min(M, N)作为该结构体的字节对齐数。结构体中每种结构的对齐数为默认对齐数P与min(M, N)的最小值min(P, min(M, N))。若结构体中包含子结构体,则先确定子结构的字节对齐数。

2.结构体中每个结构的开始都需要对齐到min(P, min(M, N))字节,若无法对齐前面会有保留的填充字节。结构体中每个结构的结束都需要对齐到下个对齐字节min(P, min(M, N)),若无法对齐则在后面填充空闲字节。

3.结构体作为一个整体存在,对于包含它的结构体来说它是一个黑盒。其内部按自己的对齐方式对齐,被包含时整体按照父结构对齐。

下面我们使用上面的规则来分析一下结构体EXAMPLE27_3的对齐方式。

#pragmapack(1)

typedef struct example27_1

{

char a;

short b;

}EXAMPLE27_1;

#pragma pack(2)

typedef struct example27_2

{

EXAMPLE27_1 a;

int b;

char c;

}EXAMPLE27_2;

#pragma pack()

typedef struct example27_3

{

char a;

EXAMPLE27_2 b;

}EXAMPLE27_3;

结构体EXAMPLE27_3中包含结构体EXAMPLE27_2,结构体EXAMPLE27_2中包含结构体EXAMPLE27_1,需要先确定结构体EXAMPLE27_1的对齐字节数。

结构体EXAMPLE27_1里面都是基本类型的变量,char型变量a对齐到1字节,short型变量b对齐到2字节,这其中最大的是2字节对齐。结构体EXAMPLE27_1使用的对齐规则是1个字节对齐,因此结构体1的字节对齐数是min(2, 1),是1字节对齐。因此,变量a对齐到min(1 ,1)=1字节,变量b对齐到min(2, 1)=1字节。

它的内存分布示意图如下:

a

b

b

EXAMPLE27_1的数据如下:

sizeof(EXAMPLE27_1)

3

OFFSET(EXAMPLE27_1, a)

0

OFFSET(EXAMPLE27_1, b)

1

结构体EXAMPLE27_2中包含了EXAMPLE27_1型的变量a、int型的变量b和char型的变量c,EXAMPLE27_1型是1字节对齐,int型是4字节对齐,char型是1字节对齐,这其中最大的是4字节对齐。结构体EXAMPLE27_2型的字节对齐规则是2字节对齐,因此结构体EXAMPLE27_2的字节对齐数是min(2, 4),是2字节对齐。因此,变量a对齐到min(1, 2)=1字节,变量b对齐到min(4, 2)=2字节,变量c对齐到min(2, 2)=2字节。变量a占用3个字节,因此在变量a之后需要有一个保留字节,变量b才能对齐到2字节,变量c之后需要保留1个空闲字节才能对齐到下一个2字节。

它的内存分布示意图如下:

a.a

a.b

a.b

b

b

b

b

c

EXAMPLE27_2的数据如下:

sizeof(EXAMPLE27_2)

10

OFFSET(EXAMPLE27_2, a.a)

0

OFFSET(EXAMPLE27_2, a.b)

1

OFFSET(EXAMPLE27_2, b)

4

OFFSET(EXAMPLE27_2, c)

8

结构体EXAMPLE27_3中包含了char型的变量a和EXAMPLE27_2型的变量b,char型是1字节对齐,EXAMPLE27_2型是2字节对齐,这其中最大的是2字节对齐。结构体EXAMPLE27_3型的字节对齐规则是4字节对齐,因此结构体EXAMPLE27_3的字节对齐数是min(4, 2),是2字节对齐。因此变量a对齐到min(1, 2)=1字节,变量b对齐到min(2, 4)=2字节。变量a占用了1个字节,因此变量a之后需要哟袷保留字节,变量b才能对齐到2字节。

它的内存分布示意图如下:

a

b.a.a

b.a.b

b.a.b

b.b

b.b

b.b

b.b

b.c

EXAMPLE27_3的数据如下:

sizeof(EXAMPLE27_3)

12

OFFSET(EXAMPLE27_3, a)

0

OFFSET(EXAMPLE27_3, b.a.a)

2

OFFSET(EXAMPLE27_3, b.a.b)

3

OFFSET(EXAMPLE27_3, b.b)

6

OFFSET(EXAMPLE27_3, b.c)

10

非字节对齐的影响

u速度影响

非字节对齐访问会比字节对齐访问花费更多的硬件访问周期,因此前者的速度也会慢一些,但处理器的设计千差万别,架构层出不穷,不同处理器非字节对齐表现出的性能也不尽相同。

下面是我测试的一组数据,测试中使用4字节对齐的指针访问非4字节对齐的地址:

ARM7TDMI

(ARMv4T)

Cortex-M3

(ARMv7-M)

Pentium(R)

Dual-Core CPU E6300

是否支持硬件对齐

硬件字节对齐访问时间

19秒

19秒

23秒

硬件非字节对齐访问时间

63秒

26秒

26秒

非字节访问效率

(字节对齐 /非字节对齐)

0.3

0.73

0.88

注:上述3种处理器的测试数据不同,不能横向比较不同处理器的访问时间,只能纵向比较同一处理器的访问时间。

从测试数据可以看出硬件非字节对齐访问确实要比硬件对齐访问速度要慢,但在不同的处理器架构上表现出的差异也是不同的。

ARM7TDMI处理器不支持硬件非字节对齐访问,需要由软件指令实现硬件非字节对齐访问,只能使用在“非字节对齐的方法”一节中的方法,将1次4字节非字节对齐硬件访问打碎成4次1字节的硬件访问,因此与软件对应的硬件指令周期数成倍增加,这也就造成了该种处理器非字节对齐访问效率是如此之低,只有0.3,几乎达到四分之一的0.25。

Cortex-M3处理器支持硬件非对齐访问,可以由一条软件指令实现1次4字节的非字节对齐硬件访问,至于硬件非字节对齐的处理部分则由硬件内部电路实现,这虽然要比硬件字节对齐访问花费更长的时间,但由于是硬件内部自动完成的,因此要比使用多条软件指令驱动硬件去完成要节省很多时间,因此效率也有了提升,达到0.73。

Intel的X86处理器E6300也支持硬件非字节对齐访问,它的非字节对齐访问效率更高,达到0.88。前2种处理器属于低端领域的处理器,而E6300则属于高端处理器,这也许是它效率最高的原因。(前2种处理器没有cache,E6300有cache,但在测试中我应该避开了cache)

u原子操作(atomic operation)影响

对于单核处理器或者是处理器的一个内核来说,硬件指令是串行执行的,从软件层次来看,它是“不能被进一步分割的最小粒子”,因此一条硬件指令不会被多线程或中断所打断,这就是原子操作。

对于不支持硬件非对齐访问的处理器若实现硬件非对齐访问就需要由多条硬件指令完成,比如说下面这个例子:

__packed int* p;

p = (int*)0x1001;

*p = 0x12345678;

为了使用4字节对齐的指针p实现对非4字节对齐的0x1001地址的访问使用了__packed,这样就将本可以使用一条硬件指令将4字节0x12345678一次写入0x1001~0x1004地址的这条指令拆分成4条对1字节访问的硬件指令,将数据0x78、0x56、0x34、0x12分别写入到0x1001、0x1002、0x1003、0x1004地址内,如果在2个线程中都有这种操作那么就破坏了原子操作,可能就会出问题。

比如说一个计数值被存储在0x1001~0x1004这4个字节里,一个线程thread_add对这个计数值进行计数自加,另一个线程thread_read读取这个计数值。

thread_add代码如下:

__packed int* p= (int*) 0x1001;

*p++;

thread_read代码如下:

__packed int* p= (int*)0x1001;

int read;

read = *p;

如果当前的计数值是0x123456FF,thread_add线程则需要将其自加到0x12345700,thread_add线程使用4次字节读取将数据0x123456FF从内存中读取到内部寄存器中,并进行自加,变成了0x12345700。然后需要再使用4次字节写入将数据0x12345700写入到0x1001~0x1004中,thread_add线程先将0x00写入到0x1001中,如果这时候发生了线程切换,切换到了thread_read线程,那么thread_read线程将从地址0x1001~0x1004中读取计数值,但此时0x1001中的数值已经被thread_add线程改写为0x00,而0x1002~ 0x1004内的数值仍为原值,因此thread_read线程读取到的数据为0x12345600,这就出错了。

对于支持硬件非字节对齐访问的处理器则不会有该问题存在,因为这种处理器的硬件非字节对齐访问是由硬件内部完成的,是一个原子操作,不会被线程打断,因此不会出错。

这篇文档拖拖拉拉的写了4个多月,为了能说的更清楚让大家看的更明白,真的费了不少力气,挺不容易的。其中有些内容涉及到处理器内部机制,处理器架构又千差万别,我没有能力找到一个全面的权威说明,因此错误也许在所难免。如有问题请到我的博客反馈,我将尽力修正blog.sina.com.cn/ifreecoding

c语言强制4字节对齐,C语言字节对齐4相关推荐

  1. c语言强制转换是四舍五入,c语言强制转换四舍五入.docx

    c语言强制转换四舍五入 在C语言中,强制转换后的数是四舍五入还是去尾?去尾要想四舍五入的话(int)(number+0.5)都是直接去尾的.当为数太多时直接截断.所以在强制转换时要很注意C语言有没有数 ...

  2. c语言强制转换字符类型,C语言中的数据类型转换问题

    C语言中的数据类型转换问题 ● 字符型变量的值实质上是一个8位的整数值,因此取值范围一般是-128-127,char型变量也可以加修饰符unsigned,则unsigned char 型变量的取值范围 ...

  3. c语言强制转换为结构体,C语言结构体的强制类型转换

    陈浩师兄03年的一篇博客<用C写有面向对象特色的程序>描述了用C语言来实现相似C++类继承的方法,这样方法的核心要点就是结构体的强制类型转换,让我来简单分析分析C语言中的结构体强制类型转换 ...

  4. c语言强制类型转换例子简单,c语言怎么进行强制类型转换

    在c语言中,可以通过"(新类型名称) 数据或表达式"语句来进行强制类型转换.强制类型转换是程序员明确提出的.需要通过特定格式的代码来指明的一种类型转换. 本教程操作环境:windo ...

  5. c语言强制转换字符类型,C语言数据类型转换实例代码

    数据类型转换就是将数据(变量.表达式的结果)从一种类型转换到另一种类型.例如,为了保存小数你可以将int类型的变量转换为double类型. 数据类型转换的一般格式为: (type_name) expr ...

  6. 程序人生 | C语言字节对齐问题详解 - 对齐/字节序/位序/网络序等(上)

    本文首发于 2014-07-21 15:32:28 1. 引言 考虑下面的结构体定义: typedef struct{char c1;short s; char c2; int i; }T_FOO; ...

  7. C语言第一个字节地址,C语言字节对齐详解

    #pragma pack () /*取消指定对齐,恢复缺省对齐*/本文引用地址:http://www.eepw.com.cn/article/148849.htm sizeof(struct D)值为 ...

  8. c语言中从字中取高低字节,关于字节对齐

    关于字节对齐 一.快速理解 1. 什么是字节对齐 在C语言中,结构是一种复合数据类型,其构成元素既可以是基本数据类型(如int.long.float等)的变量, 也可以是一些复合数据类型(如数组.结构 ...

  9. c语言中的字节序和字节对齐,C语言字节序对齐以及空间利用率

    环境:ubuntu 64bit gcc vim #include #include #include #include #include #include #include typedef struc ...

最新文章

  1. python 输入文件名查找_Python实现的根据文件名查找数据文件功能示例
  2. Android:简单的弹幕效果达到
  3. ionic 中文 API CSS and javascript link
  4. 亚马逊s3的使用方法_使用jclouds库在Amazon S3上上传
  5. python: 爬取[博海拾贝]图片脚本
  6. 为什么ElasticSearch应用开发者需要了解cluster state
  7. 2016/11/14
  8. 企业如何降低应用安全风险?
  9. R语言︱R社区的简单解析(CRAN、CRAN Task View)
  10. Java游戏程序设计教程 第4章 游戏的运行机制
  11. mac 接口压测工具jmeter的详细安装教程
  12. 基于python的毕业设计仓库库存管理系统
  13. Linux EXT4文件系统简介
  14. Android第四次作业
  15. 手机h5像素_拍照超逆天!华为P30大奖来袭 | 4000万像素+50倍变焦,漳州手机摄影界真要沸腾了......
  16. 全球与中国注塑磁体市场竞争策略分析及投资前景研究报告2021-2027年版
  17. 一键seo提交收录_百度、360快速收录新上线网站技巧、方法
  18. 什么是数据产品经理?数据产品经理与传统产品经理有什么区别?
  19. 如何清除win10右下角输入法图标?
  20. Android获取桌面应用程序

热门文章

  1. 作业提交系统无法复制而交不了作业!?巧设JavaScript来帮你
  2. python文件写入追加写入_python怎么以追加的方式写文件?
  3. Google图片存储格式WebP增加与PNG类似背景透明效果
  4. 普鲁斯特问卷的26个问题
  5. ajax中的beforesend参数说明
  6. kinect深度距离误差_azure kinect 深度相机原理
  7. CUDA-F-1-1-异构计算-CUDA
  8. [ZT]一些界面标准规范(来自因特网)
  9. HLG 1314 火影忍者之~纲手
  10. 解读--狐狸和乌鸦的故事