关于内存对齐

《关于数据对齐小结》

以下内容均摘抄于网络资源。

.关于数据的一些简介:

BSSbss segment):BSS段通常是指用来存放程序中未初始化,或初始化为0的全局变量,静态局部变量的一块内存区域。BSS是英文Block Started by Symbol的简称。BSS段属于静态内存分配。

数据段data segment):数据段通常是指用来存放程序中已初始化为非0的全局变量的一块内存区域。数据段属于静态内存分配。

代码段code segment/text segment代码段通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读, 某些架构也允许代码段为可写,即允许修改程序。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。

堆(heap):堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)

(stack)栈又称堆栈,是用户存放程序临时创建的局部变量,也就是说我们函数括弧“{}”中定义的变量(但不包括static声明的变量,static意味着在数据段中存放变量)。除此以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。由于栈的先进先出特点,所以栈特别方便用来保存/恢复调用现场。从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区。

highest address

=========

| stack |

| vv |

| |

| |

| ^^ |

| heap |

=========

| bss |

=========

| data |

=========

| text |

=========

address 0

 

 

.关于数据的排放

图一:图片

这是普通程序员心目中的内存印象,由一个个的字节组成,而CPU并不是这么看待的。

 

图二:

图片

CPU把内存当成是一块一块的,块的大小可以是24816字节大小,因此CPU在读取内存时是一块一块进行读取的。块大小成为memory access granularity(粒度) 本人把它翻译为“内存读取粒度” 。

 

假设CPU要读取一个int4字节大小的数据到寄存器中,分两种情况讨论:

 

<!--[if !supportLists]-->1<!--[endif]-->数据从0字节开始

 

<!--[if !supportLists]-->2<!--[endif]-->数据从1字节开始

 

 

 

再次假设内存读取粒度为4

 

 

 

图三:

 

图片

 

当该数据是从0字节开始时,很CPU只需读取内存一次即可把这4字节的数据完全读取到寄存器中。

 

    当该数据是从1字节开始时,问题变的有些复杂,此时该int型数据不是位于内存读取边界上,这就是一类内存未对齐的数据。

 

 

 

图四:

 

图片

 

 

 

此时CPU先访问一次内存,读取03字节的数据进寄存器,并再次读取45字节的数据进寄存器,接着把0字节和678字节的数据剔除,最后合并1234字节的数据进寄存器。对一个内存未对齐的数据进行了这么多额外的操作,大大降低了CPU性能。

这还属于乐观情况了,上文提到内存对齐的作用之一为平台的移植原因,因为以上操作只有有部分CPU肯干,其他一部分CPU遇到未对齐边界就直接罢工了。

 

 

.为什么要数据对齐?不对齐的话会产生什么样的结果?

 

1、平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。(平台移植是驱动程序开发者经常需要考虑的问题)。

 

2、性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。(就是刚才所提到的对效率的影响)。

 

 

 

.关于数据对齐细节问题:

 

1.字符串数组,起始地址不一定是四字节对齐的。对于ARM体系来说,如果访问的32位整数不是4字节对齐的,是会总线错误的(相对我们用的PC,不对齐仅仅是速度变慢)!

 

2.结构体或者联合体

 

原则1、数据成员对齐规则:结构(struct或联合union)的数据成员,第一个数据成员放在offset0的地方,以后每个数据成员存储的起始位置要从该成员大小的整数倍开始(比如int32位机为4字节,则要从4的整数倍地址开始存储)。

 

原则2、结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储。(struct a里存有struct bb里有charintdouble等元素,那b应该从8的整数倍开始存储。)

 

原则3、收尾工作:结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的整数倍,不足的要补齐。

 

很明显按照以上原则,分析之前T1T2结构的存储方式如图所示,打X的是按照规则之后的补充位

 

图片

.先简要简要介绍一下ARM处理器是如何进行数据操作的:

   ARM32位处理器,armv4能高效的处理8,16,32位的数据,但是大多数arm处理器直接操作的是32位的数据。

 

地址跳变基数为4字节即4.一次存取数据量为32位。(硬件角度),我们一次取到的32位数据不一定是一个完整

的数据构,可能是两个数据结构,也可能是某个数据结构的一部分,(而编译器帮助我们将一条对数据结构操作

C操作转化成多条对齐的汇编指令,这些从每次取到的32位数据中获得有用的值,合并重组而完成对一个数据结构的操作)

当对一个数据结构进行操作时,如边界不对齐,编译器可以将C操作转化成多条边界对齐的汇编操作,把结果合并、

重组来模拟对齐的操作(可见这种非对齐的存储是非常消耗效率的)。

软件角度,在软件方面我们定义的数据排列方式是由编译器决定的,根据编译器的对称规则进行数据排列,而常用的

数据操作指令(ARM指令中的)是以4字节为对称边界进行操作的。C中允许你干预内存对齐

     下面简要说明下内存对齐对高质量可移植代码的重要性:

     arm处理器中如果装载和存储的地址与数据类型的边界不对齐,那么可能产生异常的结果,例如:通常C编译器假定

指针是边界对齐的。如果指针不是边界对齐,那么程序执行会产生不正确的结果。这样,把代码从允许边界不对齐的

处理器移植到ARM处理器时就会出现问题。

 

六.最后再提一下对齐规则:

1)对齐规则

1.__align(num)

   这个用于修改最高级别对象的字节边界。在汇编中使用LDRD或者STRD

   就要用到此命令__align(8)进行修饰限制。来保证数据对象是相应对齐。

   这个修饰对象的命令最大是8个字节限制,可以让2字节的对象进行4字节

   对齐,但是不能让4字节的对象2字节对齐。

  __align是存储类修改,他只修饰最高级类型对象不能用于结构或者函数对象。

  

2.__packed

 

__packed是进行一字节对齐

 

  1.不能对packed的对象进行对齐

 

  2.所有对象的读写访问都进行非对齐访问

 

3.float及包含float的结构联合及未用__packed的对象将不能字节对齐

 

4.__packed对局部整形变量无影响

 

  5.强制由unpacked对象向packed对象转化是未定义,整形指针可以合法定

 

  义为packed

 

 

 

2)对齐规则

 

  每个特定平台上的编译器都有自己的默认对齐系数”(也叫对齐模数)。程序员可以通过预编译命令#pragma pack(n)

 

n=1,2,4,8,16来改变这一系数,其中的n就是你要指定的对齐系数

 

规则:

 

  1、数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset0的地方,以后每个数据

 

成员的对齐按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行。

 

  2、结构(或联合)的整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma

 

pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。

 

  3、结合12可推断:当#pragma packn值等于或超过所有数据成员长度的时候,这个n值的大小将不产生任何效果

 

 

 

posted @ 2013-05-12 16:50  wwjdwy  阅读(468)  评论(0编辑  收藏  举报