当前位置:首页 > 单片机 > 单片机
[导读]什么是字节对齐 一个变量占用 n 个字节,则该变量的起始地址必须能够被 n 整除,即: 存放起始地址 % n = 0, 对于结构体而言,这个 n 取其成员种的数据类型占空间的值最大的那个。为什么要字节对齐 内存空间是按照字

什么是字节对齐
一个变量占用 n 个字节,则该变量的起始地址必须能够被 n 整除,即: 存放起始地址 % n = 0, 对于结构体而言,这个 n 取其成员种的数据类型占空间的值最大的那个。
为什么要字节对齐
内存空间是按照字节来划分的,从理论上说对内存空间的访问可以从任何地址开始,但是在实际上不同架构的CPU为了提高访问内存的速度,就规定了对于某些类型的数据只能从特定的起始位置开始访问。这样就决定了各种数据类型只能按照相应的规则在内存空间中存放,而不能一个接一个的顺序排列。

举个例子,比如有些平台访问内存地址都从偶数地址开始,对于一个int型(假设32位系统),如果从偶数地址开始的地方存放,这样一个读周期就可以读出这个int数据,但是如果从奇数地址开始的地址存放,就需要两个读周期,并对两次读出的结果的高低字节进行拼凑才能得到这个int数据,这样明显降低了读取的效率。

如何进行字节对齐

每个成员按其类型的对齐参数(通常是这个类型的大小)和指定对齐参数(不指定则取默认值)中较小的一个对齐,并且结构的长度必须为所用过的所有对齐参数的整数倍,不够就补空字节。

这个规则有点苦涩,可以把这个规则分解一下,前半句的意思先获得对齐值后与指定对齐值进行比较,其中对齐值获得方式如下:

1. 数据类型的自身对齐值为:对于char型数据,其自身对齐值为1,对于short型为2,对于int, long, float类型,其自身对齐值为4,对于 double 类型其自身对齐值为8,单位为字节。
2.结构体自身对齐值:其成员中自身对齐值最大的那个值。

其中指定对齐值获得方式如下:

#pragma pack (value)时的指定对齐值value。

未指定则取默认值。

后半句的意思是主要是针对于结构体的长度而言,因为针对数据类型的成员,它仅有一个对齐参数,其本身的长度、于这个对齐参数,即1倍。对于结构体而言,它可能使用了多种数据类型,那么这句话翻译成对齐规则: 每个成员的起始地址 % 自身对齐值 = 0,如果不等于 0 则先补空字节直至这个表达式成立。
换句话说,对于结构体而言,结构体在在内存的存放顺序用如下规则即可映射出来:
(一)每个成员的起始地址 % 每个成员的自身对齐值 = 0,如果不等于 0 则先补空字节直至这个表达式成立;
(二)结构体的长度必须为结构体的自身对齐值的整数倍,不够就补空字节。

#pragma pack(8)
struct A{
char a;
long b;
};

struct B{
char a;
struct A b;
long c;
};

struct C{
char a;
struct A b;
double c;
};

struct D{
char a;
struct A b;
double c;
int d;
};

struct E{
char a;
int b;
struct A c;
double d;
};


对于 struct A 来说,对于char型数据,其自身对齐值为1,对于long类型,其自身对齐值为4, 结构体的自身对齐值取其成员最大的对齐值,即大小4。那么struct A 在内存中的顺序步骤为:
(1) char a, 地址范围为0x0000~0x0000,起始地址为0x0000,满足 0x0000 % 1 = 0,这个成员字节对齐了。
(2) long b, 地址起始位置不能从0x00001开始,因为 0x0001 % 4 != 0, 所以先补空字节,直到0x00003结束,即补3个字节的空字节,从0x00004开始存放b,其地址范围为0x00004~0x0007.
(3)此时成员都存放结束,结构体长度为8,为结构体自身对齐值的2倍,符合条件(二).
此时满足条件(一)和条件(二),struct A 中各成员在内存中的位置为:a*** b ,sizeof(struct A) = 8。(每个星号代表一位,成员各自代表自己所占的位,比如a占一位,b占四位)

对于struct B,里面有个类型为struct A的成员b自身对齐值为4,对于long类型,其自身对齐值为4. 故struct B的自身对齐值为4。那么struct B 在内存中的顺序步骤为:
(1) char a, 地址范围为0x0000~0x0000,起始地址为0x0000,满足 0x0000 % 1 = 0,这个成员字节对齐了。
(2) struct A b, 地址起始位置不能从0x00001开始,因为 0x0001 % 4 != 0, 所以先补空字节,直到0x00003结束,即补3个字节的空字节,从0x00004开始存放b,其地址范围为0x00004~0x00011.
(3) long c,地址起始位置从0x000012开始, 因为 0x0012 % 4 = 0,其地址范围为0x00012~0x0015.
(4)此时成员都存放结束,结构体长度为16,为结构体自身对齐值的4倍,符合条件(二).
此时满足条件(一)和条件(二),struct B 中各成员在内存中的位置为:a*** b c ,sizeof(struct C) = 24。(每个星号代表一位,成员各自代表自己所占的位,比如a占一位,b占八位,c占四位)
对于struct C,里面有个类型为struct A的成员b自身对齐值为4,对于double 类型,其自身对齐值为8. 故struct C的自身对齐值为8。那么struct C 在内存中的顺序步骤为:
(1) char a, 地址范围为0x0000~0x0000,起始地址为0x0000,满足 0x0000 % 1 = 0,这个成员字节对齐了。
(2) struct A b, 地址起始位置不能从0x00001开始,因为 0x0001 % 4 != 0, 所以先补空字节,直到0x00003结束,即补3个字节的空字节,从0x00004开始存放b,其地址范围为0x00004~0x00011.
(3) double c,地址起始位置不能从0x000012开始, 因为 0x0012 % 8 != 0,所以先补空字节,直到0x000015结束,即补4个字节的空字节,从0x00016开始存放c,其地址范围为0x00016~0x0023.
(4)此时成员都存放结束,结构体长度为24,为结构体自身对齐值的3倍,符合条件(二).
此时满足条件(一)和条件(二),struct C 中各成员在内存中的位置为:a*** b **** c ,sizeof(struct C) = 24。(每个星号代表一位,成员各自代表自己所占的位,比如a占一位,b占八位,c占八位)

对于struct D,自身对齐值为8。前面三个成员与 struct C 是一致的。对于第四成员d,因为 0x0024 % 4 = 0, 所以可以从0x0024开始存放d, 其地址范围为0x00024~0x00027.此时成员都存放结束,结构体长度为28,28 不是结构体自身对齐值8的倍数,所以要在后面补四个空格,即在0x0028~0x0031上补四个空格。补完了,结构体长度为32, 为结构体自

身对齐值的4被,,符合条件(二).
此时满足条件(一)和条件(二),struct D 中各成员在内存中的位置为:a*** b **** c d **** ,sizeof(struct D) = 32。(每个星号代表一位,成员各自代表自己所占的位,比如a占一位,b占八位,c占八位, d占四位)。

对于struct E 中各成员在内存中的位置为:a*** b c d, sizeof(struct E) = 24。(每个星号代表一位,成员各自代表自己所占的位,比如a占一位,b占四位,c占八位, d占八位)。
通过struct D 和 struct E 可以看出,在成员数量和类型一致的情况,后者的所占空间少于前者,因为后者的填充空字节要少。如果我们在编程时考虑节约空间的话,应该遵循将变量按照类型大小从小到大声明的原则, 这样尽量减少填补空间。另外,可以在填充空字节的地方来插入reserved成员, 例如
struct A
{
char a;
char reserved[3];
int b;
};
这样做的目的主要是为了对程序员起一个提示作用,如果不加则编译器会自动补齐。

习题

typedef struct

{

int a; //ARM(int=4) 51(int=2)

char b; // 1

short c; // 2

}AAA;

typedef struct

{

char b;

int a;

short c;

}BBB;

i = sizeof(AAA);

j = sizeof(BBB);

//注意在51单片机,ARM,PC不同

51 (i=j=5) //好像强制单字节对齐

ARM(i=8,j=12) //按规则默认对齐

PC(i=8,j=12)

AAA对齐方式如下(ARM)

I I I I

I0 I I

BBB对齐方式如下(ARM)

I 0 0 0

II I I

I I 0 0

通过#pragma pack可以调整对齐字节数

#pragma pack(1) //指定Align为 1字节;

。。。。。。。。。。。。//需要对齐的结构体

#pragma pack() //恢复到原先值


本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

8位单片机在嵌入式设计领域已经成为半个多世纪以来的主流选择。尽管嵌入式系统市场日益复杂,8位单片机依然不断发展,积极应对新的挑战和系统需求。如今,Microchip推出的8位PIC®和AVR®单片机系列,配备了先进的独立...

关键字: 单片机 嵌入式 CPU

在嵌入式系统开发中,程序烧录是连接软件设计与硬件实现的关键环节。当前主流的单片机烧录技术已形成ICP(在电路编程)、ISP(在系统编程)、IAP(在应用编程)三大技术体系,分别对应开发调试、量产烧录、远程升级等不同场景。...

关键字: 单片机 ISP ICP IAP 嵌入式系统开发

在嵌入式系统开发中,看门狗(Watchdog Timer, WDT)是保障系统可靠性的核心组件,其初始化时机的选择直接影响系统抗干扰能力和稳定性。本文从硬件架构、软件流程、安全规范三个维度,系统分析看门狗初始化的最佳实践...

关键字: 单片机 看门狗 嵌入式系统

本文中,小编将对单片机予以介绍,如果你想对它的详细情况有所认识,或者想要增进对它的了解程度,不妨请看以下内容哦。

关键字: 单片机 开发板 Keil

随着单片机系统越来越广泛地应用于消费类电子、医疗、工业自动化、智能化仪器仪表、航空航天等各领域,单片机系统面临着电磁干扰(EMI)日益严重的威胁。电磁兼容性(EMC)包含系统的发射和敏感度两方面的问题。

关键字: 单片机 电磁兼容

以下内容中,小编将对单片机的相关内容进行着重介绍和阐述,希望本文能帮您增进对单片机的了解,和小编一起来看看吧。

关键字: 单片机 复位电路

在这篇文章中,小编将为大家带来单片机的相关报道。如果你对本文即将要讲解的内容存在一定兴趣,不妨继续往下阅读哦。

关键字: 单片机 异常复位

今天,小编将在这篇文章中为大家带来单片机的有关报道,通过阅读这篇文章,大家可以对它具备清晰的认识,主要内容如下。

关键字: 单片机 仿真器

单片机将是下述内容的主要介绍对象,通过这篇文章,小编希望大家可以对它的相关情况以及信息有所认识和了解,详细内容如下。

关键字: 单片机 中断 boot

一直以来,单片机都是大家的关注焦点之一。因此针对大家的兴趣点所在,小编将为大家带来单片机的相关介绍,详细内容请看下文。

关键字: 单片机 数字信号 模拟信号
关闭