从上例可以看出,所有对结构体域成员的访问都是通过字节访问实现的,所以这种不对齐内存访问无论从代码占用的存储器空间,还是代码的执行时间上都要付出一定的代价。
然而,开发者可以给编译器提供更多的信息,使其知道结构体内哪个字段是对齐的,哪个字段不是。为此,必须将未对齐字段声明为__packed,并从struct本身除去__packed属性。通过这种方法可以保证对struct中自然对齐成员的快速访问。而且,哪个字段是未对齐的也更清楚,但这样就增加了访问struct结构的难度,当用户从结构中增加或删除字段时需要特别小心。
修改上例中结构体的定义,来减少访问结构体的开销。具体代码如下所示。
struct mystruct {
int aligned_i;
short aligned_s;
__packed int unaligned_i;
};
struct mystruct S1;
对修改后的程序进行编译,产生的汇编代码如下所示。
MOV r2,r0
LDR r0,|L1.32|
STR r2,[r0,#0]
STRH r1,[r0,#4]
LDMIB r0,{r3,r12}
MOV r0,r3,LSR #16
ORR r0,r0,r12,LSL #16
BX lr
从编译后的汇编代码不难看出,对结构体内符号自然边界对齐的域,编译器直接使用相应的Load/Store指令进行访问,而只有那些非自然边界对齐的域,编译器才进行附加处理。这样,从时间和空间两方面减小了程序的开销。
同一原理也适应于联合体结构(unions)。使用在存储器中未对齐的联合组件的__packed属性。
13.1.3 用于半字存取的非对齐 LDR指令
一些特殊情况下,ARM编译程序可以生成非对齐LDR指令。特别是编译程序从存储器中载入半字时将使用该方法。这是因为,通过使用相应地址,所需的半字可以载入到寄存器的高半段(bits[31:16]),然后通过移位,将有效数据移到寄存器的低半段(bits[15:0])。这样做的目的是通过减少内存访问次数来减少程序的执行时间。通过上面的方法,程序只需要一次存储器的访问,而使用LDRB指令做同样的操作需要两次存储器的存取,而且还要为将这两个字节合并在一起添加特殊的代码。在ARM体系结构v3和其早期版本中,通常使用该方法进行所有的半字载入。但在ARMv4及其以后版本中,出现了专门的半字载入指令,这种方法逐渐被取代。但是,非对齐LDR指令仍可能会出现,比如在一个充填结构中存取一个非对齐short域类型。
|
注意 |
在RVCT中已经不再支持ARMv3架构。 |
13.1.4 移植代码并检测非对齐内存访问
在非RISC体系结构的处理器上执行的代码中,可能会存在使用指针访问非自然边界对齐的数据类型。这种操作,在ARM体系结构中是不允许的。这就给代码的移植带来很大困难。用户必须识别并更改此类内存访问代码才能使其在RISC体系结构的处理器上正确执行。
识别非对齐存取可能会很困难,因为使用非对齐地址进行的载入或存储操作会产生不正确的动作。追踪到底是哪部分的C源程序造成了这个问题是很困难的。
具有完整存储器管理单元(MMUs)的ARM处理器,例如ARM920TTM,支持内存对齐检测功能,用户可以通过设置MMU使处理器检测每一次的内存访问以确保其被正确地对齐。如果出现非对齐内存访问,MMU将产生数据中断。这样就给追踪出错代码带来了很大的方便。
对于一些简单的没有MMU的内核,如ARM7TDMI,最好的方法是在ASIC(Application Specific Integrated Circuit)/ASSP(Application Specific Standard Product)内部实现对齐检测。可以增加专门的ARM内核扩展硬件,由其监控每次数据的访问的内存大小和存取地址总线的最低有效位。在非对齐存取的情况下,可以通过配置ASIC/ASSP产生中断信号(ABORT)。ARM公司建议在需要运行移植代码设备中包含这样的ASIC/ASSP逻辑。
如果在设计系统时,将系统设计成为当出现非对齐的内存访问时产生异常,则必须安装数据中断异常处理程序(Data Abort Handler)。出现非对齐存取时,程序进入数据中断处理程序,并由此识别位于返回地址(在LR中保存的地址)减8(r14-8)的出错数据存取指令。
一旦出现数据中断异常,必须通过改变C源程序来修复非对齐的数据访问。使用下列指令可有条件地完成修复:
#ifdef __arm
#define PACKED __packed
#else
#define PACKED
#endif
:
PACKED int *pi;
:
由于代码大小和性能上的开销,最好尽可能少采用存取非对齐数据。
ARM编译器支持--pointer_alignment和--min_array_alignment与内存对齐相关的编译选项,详见ARM相关文档。