内存管理解析(2) 字节对齐详解

很想贴出转载链接, 找不到原来看的资料了, 只有从个人记录的笔记上挪过来, 如有错误请留言指正

目录

一. 什么是字节对齐

二. 为什么要字节对齐

 

三. 有哪些对齐形式

1. 结构体对齐

对齐值:

对齐准则:

对齐的隐患:

更改对齐方式

2. 栈内存对齐

3. 位域对齐

位域说明

使用场景

对齐规则

注意事项

四. linux内存分配对齐


一. 什么是字节对齐

内存空间按照字节划分,理论上可以从任何起始地址访问任意类型的变量。

但实际中在访问特定类型变量时经常在特定的内存地址访问,这就需要各种类型数据按照一定的规则在空间上排列,而不是顺序一个接一个地存放,这就是对齐。

二. 为什么要字节对齐

平台原因(移植原因):不同硬件平台对存储空间的处理上存在很大的不同。某些平台对特定类型的数据只能从特定地址开始存取,而不允许其在内存中任意存放。

性能原因: 数据结构(尤其是栈),应该尽可能在自然边界上对齐,因为在访问为对齐的内存时,处理器需要访问两次,而对齐的内存处理器只需要访问一次。

借个图做说明: 32位的Intel处理器通过总线访问(包括读和写)内存数据。每个总线周期从偶地址开始访问32位内存数据,内存数据以字节为单位存放。如果一个32位的数据没有存放在4字节整除的内存地址处,那么处理器就需要2个总线周期对其进行访问,显然访问效率下降很多。

 

三. 有哪些对齐形式

1. 结构体对齐

对齐值:

     1) 数据类型自身的对齐值:char型数据自身对齐值为1字节,short型数据为2字节,int/float型为4字节,double型为8字节。(另外需要注意在 64位系统和32位系统下 指针类型和long类型 大小不同 32/64位系统下 4/8 字节大小)

     2) 结构体或类的自身对齐值:其成员中自身对齐值最大的那个值。

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

     4) 数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中较小者,即有效对齐值=min{自身对齐值,当前指定的pack值}。

 

对齐准则:

结构体字节对齐的细节和具体编译器实现相关,但一般而言满足三个准则:

     1) 结构体变量的首地址能够被其最宽基本类型成员的大小所整除;

     2) 结构体每个成员相对结构体首地址的偏移量(offset)都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节(internal adding);

     3) 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一个成员之后加上填充字节{trailing padding}。

举例如下:

struct test1{
    char     a;
    double   b;
    int      c;
};

按照对齐准则: 第一条先不管, 因为第二条规则 double  b存储位置为结构体首地址偏移8字节, 按照第三条规则, 需要在int c 后面补齐4 字节, 所以整个结构体大小为 3*8 = 24 字节, 见下左图.  如果把int c 放到第二位的话,  对齐方式如下右图.

 

所以: 为了减少结构体对齐导致的内存消耗, 定义结构体时一般按照成员类型从大到小排列. (从小到大也行)

对齐的隐患:

数据类型转换:强转数据类型时可能会从奇数边界访问内存

处理器间数据通信:需要注意字节对齐和字节序的问题

 

如果出现对齐或者赋值问题可查看:

     1) 编译器的字节序大小端设置;

     2) 处理器架构本身是否支持非对齐访问;

     3) 如果支持看设置对齐与否,如果没有则看访问时需要加某些特殊的修饰来标志其特殊访问操作

 

更改对齐方式

     主要是更改C编译器的缺省字节对齐方式。   

     在缺省情况下,C编译器为每一个变量或是数据单元按其自然对界条件分配空间。一般地,可以通过下面的方法来改变缺省的对界条件:

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

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

 

另外,还有如下的一种方式(GCC特有语法):

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

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

【注】__attribute__机制是GCC的一大特色,可以设置函数属性(Function Attribute)、变量属性(Variable Attribute)和类型属性(Type Attribute)。详细介绍请参考:    http://www.unixwiz.net/techtips/gnu-c-attributes.html

 

2. 栈内存对齐

在VC/C++中,栈的对齐方式不受结构体成员对齐选项的影响。总是保持对齐且对齐在4/8字节边界上。

也就是说变量的内存地址总是4/8的倍数

以多少字节对齐取决于是 32位 还是 64位, 具体的话又涉及到系统和硬件, 这里不多讲, 知道有这么个事儿就行.

 

3. 位域对齐

位域说明

有些信息在存储时,并不需要占用一个完整的字节,而只需占几个或一个二进制位。

位域是一种特殊的结构成员或联合成员(即只能用在结构或联合中),用于指定该成员在内存存储时所占用的位数,从而在机器内更紧凑地表示数据

 

位域在本质上就是一种结构类型,不过其成员是按二进位分配的。位域变量的说明与结构变量说明的方式相同,可先定义后说明、同时定义说明或直接说明。      

 

使用场景

1) 当机器可用内存空间较少而使用位域可大量节省内存时。如把结构作为大数组的元素时。

2) 当需要把一结构体或联合映射成某预定的组织结构时。如需要访问字节内的特定位时

 

对齐规则

位域成员不能单独被取sizeof值。

    C99规定int、unsigned int和bool可以作为位域类型,但编译器几乎都对此作了扩展,允许其它类型的存在。位域作为嵌入式系统中非常常见的一种编程工具,优点在于压缩程序的存储空间。

 

     1) 如果相邻位域字段的类型相同,且其位宽之和小于类型的sizeof大小,则后面的字段将紧邻前一个字段存储,直到不能容纳为止;

     2) 如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍;

     3) 如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,VC6采取不压缩方式,Dev-C++和GCC采取压缩方式;

     4) 如果位域字段之间穿插着非位域字段,则不进行压缩;

     5) 整个结构体的总大小为最宽基本类型成员大小的整数倍,而位域则按照其最宽类型字节数对齐

 

注意事项

1) 位域的地址不能访问,因此不允许将&运算符用于位域。不能使用指向位域的指针也不能使用位域的数组(数组是种特殊指针)。

2) 位域不能作为函数返回的结果。

3) 位域以定义的类型为单位,且位域的长度不能够超过所定义类型的长度。例如定义int a:33是不允许的。

4) 位域可以不指定位域名,但不能访问无名的位域。

5) 位域的表示范围。

位域的赋值不能超过其可以表示的范围;

位域的类型决定该编码能表示的值的结果。

6) 带位域的结构在内存中各个位域的存储方式取决于编译器,既可从左到右也可从右到左存储。

7) 位域的实现会因编译器的不同而不同,使用位域会影响程序可移植性。因此除非必要否则最好不要使用位域。

8) 尽管使用位域可以节省内存空间,但却增加了处理时间。当访问各个位域成员时,需要把位域从它所在的字中分解出来或反过来把一值压缩存到位域所在的字位中。

 

四. linux内存分配对齐

GNU网站中把glibc源码下载下来,查看其 malloc.c文件,整理关键信息如下图

request2size负责内存对齐操作,MINSIZE是malloc时内存占用的最小内存单元,32位系统为16字节,64位系统为32字节,MALLOC_ALIGNMENT为内存对齐字节数,由于在32和64位系统中,size_t为4字节和8字节,所以MALLOC_ALIGNMENT在32位和64位系统中,分别为8和16.

 

实际上,对齐参数(MALLOC_ALIGNMENT)大小的设定需要满足以下两点:

1. 必须是2的幂

2. 必须是void *的整数倍

 

所以从request2size可知,在64位系统,如果申请内存为1~24字节,系统内存消耗32字节,当申请25字节的内存时,系统内存消耗48字节。而对于32位系统,申请内存为1~12字节时,系统内存消耗为16字节,当申请内存为13字节时,系统内存消耗为24字节

一般他们的差距是一个指针大小,计算公式是

max(MINSIZE,in_use_size) 

其中in_use_size=(要求大小+2*指针大小-指针大小)align to MALLOC_ALIGNMENT

(对于上面计算的由来可以参见glibc 内存池管理 ptmalloc这篇文章的第4节chuck部分以及搜一下malloc的内部实现源码 )

  • 2
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值