ARM 系列处理器是 RISC (Reducded Instruction Set Computing)处理器。很多基于ARM的高效代码的程序设计策略都源于RISC 处理器。和很多 RISC 处理器一样,ARM 系列处理器的内存访问,也要求数据对齐,即存取“字(Word)”数据时要求四字节对齐,地址的bits[1:0]==0b00;存取“半字(Halfwords)”时要求两字节对齐,地址的bit[0]==0b0;存取“字节(Byte)”数据时要求该数据按其自然尺寸边界(Natural Size Boundary)定位。

  ARM 编译程序通常将全局变量对齐到自然尺寸边界上,以便通过使用 LDR和 STR 指令有效地存取这些变量。这种内存访问方式与多数 CISC (Complex Instruction Set Computing)体系结构不同,在CISC体系结构下,指令直接存取未对齐的数据。因而,当需要将代码从CISC 体系结构向 ARM 处理器移植时,内存访问的地址对齐问题必须予以注意。在RISC体系结构下,存取未对齐数据无论在代码尺寸或是程序执行效率上,都将付出非常大的代价。

  本文将从以下几个方面讨论在ARM体系结构下的程序设计问题。

  未对齐的数据指针

  C和C++编程标准规定,指向某一数据类型的指针,必须和该类型的数据地址对齐方式一致,所以ARM 编译器期望程序中的 C 指针指向存储器中字对齐地址,因为这可使编译器生成更高效的代码。

  比如,如果定义一个指向 int 数据类型的指针,用该指针读取一个字,ARM 编译器将使用LDR 指令来完成此操作。如果读取的地址为四的倍数(即在一个字的边界)即能正确读取。但是,如果该地址不是四的倍数,那么,一条 LDR 指令返回一个循环移位结果,而不是执行真正的未对齐字载入。循环移位结果取决于该地址向对于字的边界的偏移量和系统所使用的端序(Endianness)。例如,如果代码要求从指针指向的地址 0x8006 载入数据,即要载入 0x8006、0x8007、0x8008 和 0x8009 四字节的内容。但是,在 ARM 处理器上,这个存取操作载入了0x8004、0x8005、0x8006 和 0x8007 字节的内容。这就是在未对齐的地址上使用指针存取所得到的循环移位结果。

  因而,如果想将指针定义到一个指定地址(即该地址为非自然边界对齐),那么在定义该指针时,必须使用 __packed 限定符来定义指针: 例如,

  __packed int *pi; // 指针指向一个非字对其内存地址

  使用了_packed限定符限定之后,ARM 编译器将产生字节存取命令(LDRB或STRB指令)来存取内存,这样就不必考虑指针对齐问题。所生成的代码是字节存取的一个序列,或者取决于编译选项、跟变量对齐相关的移位和屏蔽。但这会导致系统性能和代码密度的损失。

  值得注意的是,不能使用 __packed 限定的指针来存取存储器映射的外围寄存器,因为 ARM 编译程序可使用多个存储器存取来获取数据。因而,可能对实际存取地址附近的位置进行存取,而这些附近的位置可能对应于其它外部寄存器。当使用了位字段(Bitfield)时, ARM 程序将访问整个结构体,而非指定字段。

 

在ARM中,通常希望字单元的地址是字对齐的(地址的低两位为0b00),半字单元的地址是半字对齐的(地址的最低为0b0).在存储访问操作中,如果存储单元的地址没有遵守上述的对齐规则,则称为非对齐(unaligned)的存储访问操作.


代码中关于对齐的隐患,很多是隐式的。比如在强制类型转换的时候。例如:

unsigned int i = 0×12345678;

unsigned char *p=NULL;

unsigned short *p1=NULL;

p=&i;

*p=0×00;

p1=(unsigned short *)(p+1);

*p1=0×0000;

最后两句代码,从奇数边界去访问unsignedshort型变量,显然不符合对齐的规定。

在x86上,类似的操作只会影响效率,但是在MIPS或者sparc上,可能就是一个error,因为它们要求必须字节对齐.


有部分摘自ARM编译器文档对齐部分

对齐的使用:

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。

    __packed int* p;  //__packed int 则没有意义

 6.对齐或非对齐读写访问带来问题

 __packed struct STRUCT_TEST

{

 char a;

 int b;

 char c;

}  ;    //定义如下结构此时b的起始地址一定是不对齐的

        //在栈中访问b可能有问题,因为栈上数据肯定是对齐访问[from CL]

//将下面变量定义成全局静态不在栈上 

static char* p;

static struct STRUCT_TEST a;

void Main()

{

__packed int* q;  //此时定义成__packed来修饰当前q指向为非对齐的数据地址下面的访问则可以

p = (char*)&a;          

q = (int*)(p+1);      

*q = 0×87654321; 

/*   

得到赋值的汇编指令很清楚

ldr      r5,0×20001590 ; = #0×12345678

[0xe1a00005]   mov      r0,r5

[0xeb0000b0]   bl       __rt_uwrite4  //在此处调用一个写4byte的操作函数 

     

[0xe5c10000]   strb     r0,[r1,#0]   //函数进行4次strb操作然后返回保证了数据正确的访问

[0xe1a02420]   mov      r2,r0,lsr #8

[0xe5c12001]   strb     r2,[r1,#1]

[0xe1a02820]   mov      r2,r0,lsr #16

[0xe5c12002]   strb     r2,[r1,#2]

[0xe1a02c20]   mov      r2,r0,lsr #24

[0xe5c12003]   strb     r2,[r1,#3]

[0xe1a0f00e]   mov      pc,r14

*/

/*

如果q没有加__packed修饰则汇编出来指令是这样直接会导致奇地址处访问失败

[0xe59f2018]   ldr      r2,0×20001594 ; = #0×87654321

[0xe5812000]   str      r2,[r1,#0]

*/