字节对齐

Posted 大黑耗

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了字节对齐相关的知识,希望对你有一定的参考价值。

原文:https://blog.csdn.net/cclethe/article/details/79659590(稍有修改)

基本概念
许多计算机系统对基本数据类型合法地址做出了一些限制,要求某种类型对象的地址必须是某个值K(通常是2,4或8)的倍数。这种对齐限制简化了形成处理器和存储器系统之间的接口的硬件设计。对齐跟数据在内存中的位置有关。

如果一个变量的内存地址正好位于它长度的整数倍,他就被称做自然对齐。比如在32位cpu下,假设一个整型变量的地址为0x00000004,那它就是自然对齐的。

为什么要字节对齐
需要字节对齐的根本原因在于CPU访问数据的效率问题。例如,假设一个处理器在读取数据的时候,比如他要读取一个long类型的数据,因为long长度为8字节,所以它读取数据的方式是从0地址开始每次以8字节为单位向这个long数据的地址前进,直到前进到这个数据的地址,并从这个地址开始读取出8字节,所以如果这个地址是8的倍数的话,就可以一次性把数据读出来,否则,我们可能需要执行两次存储器访问,因为对象可能被分放在两个8字节存储块中

另外,假设一个int变量的地址不是自然对齐(int长度4字节,这个地址不是4的倍数),比如为0x00000002,则CPU如果取它的值的话需要访问两次内存,以short的长度(2字节)为步长,第一次取从0x00000002-0x00000003的一个short,第二次取从0x00000004-0x00000005的一个short然后组合得到所要的数据;如果变量在0x00000003地址上的话则要访问三次内存,第一次为char,第二次为short,第三次为char,然后组合得到整型数据。而如果变量在自然对齐位置上,则只要一次就可以取出数据。

如何处理字节对齐
先让我们看编译器是按照什么样的原则进行对齐的:

数据类型自身的对齐值:为指定平台上基本类型的长度。对于char型数据,其自身对齐值为1,对于short型为2,对于int,float,double类型,其自身对齐值为4,单位字节。
结构体或者类的自身对齐值:其成员中自身对齐值最大的那个值。
指定对齐值:#pragma pack (value)时的指定对齐值value。
数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中小的那个值。
对于标准数据类型,它的地址只要是它的长度的整数倍就行了,而非标准数据类型按下面的原则对齐:
数组 :按照基本数据类型对齐,第一个对齐了后面的自然也就对齐了。
联合 :按其包含的长度最大的数据类型对齐。
结构体: 结构体中每个数据类型都要对齐。

当数据类型为结构体时,编译器可能需要在结构体字段的分配中插入间隙,以保证每个结构元素都满足它的对齐要求第一个数据变量的起始地址就是数据结构的起始地址。结构体的成员变量要对齐排放(对于非对齐成员需要在其前面填充一些字节,保证其在对齐位置上),结构体本身也要根据自身的有效对齐值圆整(就是结构体总长度需要是结构体有效对齐值的整数倍),此时可能需要在结构末尾填充一些空间,以满足结构体整体的对齐—-向结构体元素中最大的元素对齐。

通过上面的分析,对结构体进行字节对齐,我们需要知道四个值:

指定对齐值:代码中指定的对齐值,记为packLen
默认对齐值:结构体中每个数据成员及结构体本身都有默认对齐值,记为defaultLen
成员偏移量:即相对于结构体起始位置的长度,记为offset
成员长度:结构体中每个数据成员的长度(注结构体成员为补齐之后的长度),记为memberLen
及两个规则:

对齐规则: offset % vaildLen = 0,其中vaildLen为有效对齐值vaildLen = min(packLen, defaultLen);
填充规则: 如成员变量不遵守对齐规则,则需要对其补齐;在其前面填充一些字节保证该成员对齐。需填充的字节数记为pad

 

C语言-字节对齐

什么是字节对齐

字节对齐作用

    字节对齐的作用不仅是便于cpu快速访问,同时合理的利用字节对齐可以有效地节省存储空间。

    对于32位机来说,4字节对齐能够使cpu访问速度提高,比如说一个long类型的变量,如果跨越了4字节边界存储,那么cpu要读取两次,这样效率就低了。但是在32位机中使用1字节或者2字节对齐,反而会使变量访问速度降低。所以这要考虑处理器类型,另外还得考虑编译器的类型。在vc中默认是4字节对齐的,GNU gcc 也是默认4字节对齐。

字节对齐的方式

1.  使用伪指令#pragma pack

  • 使用伪指令#pragma pack (n),C编译器将按照n个字节对齐。

  • 使用伪指令#pragma pack (),取消自定义字节对齐方式。

    需要注意的是,#pragma pack 


结构体对齐规则举例:

摘自https://blog.csdn.net/lime1991/article/details/44536343

结构体中各个成员按照它们被声明的顺序在内存中顺序存储。

1)将结构体内所有数据成员的长度值相加,记为sum_a; 
2)将各数据成员内存对齐,按各自对齐模数而填充的字节数累加到和sum_a上,记为sum_b。对齐模数是【该数据成员所占内存】与【#pragma pack指定的数值】中的较小者。
3)将和sum_b向结构体模数对齐,该模数是【#pragma pack指定的数值】、【未指定#pragma pack时,系统默认的对齐模数4字节】和【结构体内部最大的基本数据类型成员】长度中数值较小者。结构体的长度应该是该模数的整数倍。

3.1 基本数据类型所占内存大小




以下例子均按32bit编译器处理。

3.2 Test1

#pragma pack(4)struct Test1{ char c; short sh; int a; float f; int *p; char *s; double d;};

上述结构体总共占28Bytes。

  • c的偏移量为0,占1个Byte。

  • a占4个Byte,对齐模数是4,因此接在sh后存放即可,偏移量为4。






  2. 使用 __attribute__属性


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

  •  __attribute__ ((packed)),取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐。

typedef struct{ uint8_t len; uint8_t is_need_update; uint8_t data[3]; uint64_t padding;} v_keys_stg __attribute__((aligned(8)));
static v_keys_stg g_vk_data = {0};static v_keys_stg g_vm_data = {0};
app.map文件Symbol Name   addr         type size objectg_vk_data 0x00211a30 Data 16 key_handle.o(.bss)g_vm_data 0x00211a40 Data 16 key_handle.o(.bss)


__attribute__设置的值进行对齐变化,但是实际的占用的size大小没有变化的;

以上是关于字节对齐的主要内容,如果未能解决你的问题,请参考以下文章

字节对齐方法

字节对齐

指定字节对齐方式

仔细讨论 C/C++ 字节对齐问题

#pragma pack(非常有用的字节对齐用法说明)

关于字节对齐的理解