理一理C语言字节对齐的那些事

栏目: C · 发布时间: 7年前

内容简介:关注公众号【编程珠玑】,获取更多Linux/C/C++/Python/Go/算法/工具等原创技术文章。

前言

字节对齐是我们初学 C语言 就会接触到的一个概念,但是到底什么是字节对齐?对齐准则又是什么?为什么要字节对齐呢?字节对齐对我们编程有什么启示?本文将简单理一理字节对齐的那些事。

什么是字节对齐

计算机中内存大小的基本单位是字节(byte),理论上来讲,可以从任意地址访问某种基本数据类型,但是实际上,计算机并非逐字节大小读写内存,而是以2,4,或8的 倍数的字节块来读写内存,如此一来就会对基本数据类型的合法地址作出一些限制,即它的地址必须是2,4或8的倍数。那么就要求各种数据类型按照一定的规则在空间上排列,这就是对齐。

对齐准则是什么

总的来说,字节对齐有以下准则:

  • 结构体变量的首地址能够被其最大基本类型成员字节数大小所整除。

  • 结构体每个成员相对结构体首地址的偏移都是成员大小的整数倍,如不满足,对前一个成员填充字节以满足。

  • 结构体的总大小为结构体对最大成员大小的整数倍,如不满足,最后填充字节以满足。

我们通过一个小例子来说明是如何对齐的。

考虑下面的程序:

/*================================================================
*   Copyright (C) 2018  Ltd. All rights reserved.
*   
*   文件名称:testByteAlign.c
*   创 建 者:shouwang
*   创建日期:2018年09月15日
*   描    述:
*
================================================================*/
#include<stdio.h>
#include<stdint.h>
struct test
{
    int a;
    char b;
    int c;
    short d;
};
int main(int argc,char *argv)
{
    /*在32位和64位的机器上,size_t的大小不同*/
    printf("the size of struct test is %zu\n",sizeof(struct test));
    return 0;
}

编译成32位程序并运行(默认四字节自然对齐),可以看到,结构体test 的大小为16字节,而不是11字节(a占4字节,b占1字节,c占4字节,d占2字节)

#64位机器上编译32位程序可能需要安装一个库
#sudo apt-get install gcc-multilib
gcc -m32 -o testByteAlign testByteAlign.c #编译程序
chmod +x testByteAlign  #赋执行权限
./testByteAlign  #运行
the size of struct test is 16

实际上,结构体test的成员在内存中可能是像下面这样分布的(数值为偏移量)

未对齐时:

0~3 4 5~9 10~11
a b c d

对齐时:

0~3 4 5~7 8~11 12~13 14~15
a b 填充内容 c d 填充内容

从上面可以看出,c的偏移为5,不满足对齐要求(它的偏移量应该能够被sizeof(int)大小整除),因此在b后面填充了3个字节,使得c的偏移为8。在b后面填充后,d已经满足对齐要求了,为什么最后还要填充字节呢?或者说,为什么需要满足第三条准则呢?

考虑下面的声明

struct teArray[2];

我们不难知道,teArray[0]的d如果不填充字节,那么teArray[1]的a偏移为14,不满足对齐要求,因此d后面也需要填充字节。

为什么要字节对齐

无论数据是否对齐,大多数计算机还是能够正确工作,而且从前面可以看到,结构体test本来只需要11字节的空间,最后却占用了16字节,很明显 浪费了空间 ,那么为什么还要进行字节对齐呢?最重要的考虑是 提高内存系统性能

前面我们也说到,计算机每次读写一个字节块,例如,假设计算机总是从内存中取8个字节,如果一个double数据的地址对齐成8的倍数,那么一个内存操作就可以读或者写,但是如果这个double数据的地址没有对齐,数据就可能被放在两个8字节块中,那么我们可能需要执行两次内存访问,才能读写完成。显然在这样的情况下,是低效的。所以需要字节对齐来提高内存系统性能。

在有些处理器中,如果需要未对齐的数据,可能不能够正确工作甚至crash,这里我们不多讨论。

实际编程中的考虑

实际上,字节对齐的细节都由编译器来完成,我们不需要特意进行字节的对齐,但并不意味着我们不需要关注字节对齐的问题。

空间存储

还是考虑前面的结构体test,其占用空间大小为16字节,但是如果我们换一种声明方式,调整变量的顺序,重新运行程序,最后发现结构体test占用大小为 12字节

struct test
{
    int a;
    char b;
    short d;
    int c;
};

空间存储情况如下,b和c存储在了一个字节快中:

0~3 4 5 6~7 8~11
a b 填充内容 c d

也就是说,如果我们在设计结构的时候,合理调整成员的位置,可以大大节省存储空间。但是需要在空间和可读性之间进行权衡。

跨平台通信

由于不同平台对齐方式可能不同,如此一来,同样的结构在不同的平台其大小可能不同,在无意识的情况下,互相发送的数据可能出现错乱,甚至引发严重的问题。因此,为了不同处理器之间能够正确的处理消息,我们有两种可选的处理方法。

  • 1字节对齐

  • 自己对结构进行字节填充

我们可以使用伪指令#pragma pack(n)(n为字节对齐数)来使得结构间一字节对齐。

同样是前面的程序,如果在结构体test的前面加上伪指令,即如下:

#pragma pack(1) /*1字节对齐*/
struct test
{
    int a;
    char b;
    int c;
    short d;
};
#pragma pack()/*还原默认对齐*/

在这样的声明下,任何平台结构体test的大小都为11字节,这样做能够保证跨平台的结构大小一致,同时还节省了空间,但不幸的是,降低了效率。

当然了对于单个结构体,如下的方法,使其1字节对齐

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

注:

  • __attribute__((aligned (n))),让所作用的结构成员对齐在n字节自然边界上。如果结构中有成员的长度大于n,则按照最大成员的长度来对齐。

  • __attribute__ ((packed)),取消结构在编译过程中的优化对齐,也可以认为是1字节对齐。

除了前面的1字节对齐,还可以进行人为的填充,即test结构体声明如下:

struct test
{
    int a;
    char b;
    char reserve[3];
    int c;
    short d;
    char reserve1[2];
};

访问效率高,但并不节省空间,同时扩展性不是很好,例如,当字节对齐有变化时,需要填充的字节数可能就会发生变化。

总结

虽然我们不需要具体关心字节对齐的细节,但是如果不关注字节对齐的问题,可能会在编程中遇到难以理解或解决的问题。因此针对字节对齐,总结了以下处理建议:

  • 结构体成员合理安排位置,以节省空间

  • 跨平台数据结构可考虑1字节对齐,节省空间但影响访问效率

  • 跨平台数据结构人为进行字节填充,提高访问效率但不节省空间

  • 本地数据采用默认对齐,以提高访问效率

  • 32位与64位默认对齐数不一样     

思考

下面的结构体使用sizeof得到的大小是多少?

struct test
{
    char a;
    char b;
};

关注公众号【编程珠玑】,获取更多Linux/C/C++/Python/Go/算法/工具等原创技术文章。 后台免费获取经典电子书和视频资源

理一理C语言字节对齐的那些事


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

高性能JavaScript

高性能JavaScript

【美】Nicholas C. Zakas(尼古拉斯.泽卡斯) / 丁琛 / 电子工业出版社 / 2015-8-1 / 65

如果你使用 JavaScript 构建交互丰富的 Web 应用,那么 JavaScript 代码可能是造成你的Web应用速度变慢的主要原因。《高性能JavaScript》揭示的技术和策略能帮助你在开发过程中消除性能瓶颈。你将会了解如何提升各方面的性能,包括代码的加载、运行、DOM 交互、页面生存周期等。雅虎的前端工程师 Nicholas C. Zakas 和其他五位 JavaScript 专家介绍......一起来看看 《高性能JavaScript》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具