ARM 系列处理器是 RISC++” target=”_blank”>C++ (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]
*/