由于 ARM 加载即时数据的能力有限,为 ARM 生成代码的实用程序经常将代码和数据并置。例如,像这样的语句
void myRoutine(void)
{
myVar1=0x12345678;
myVar2=0x87654321;
}
最终可能是这样的:
myRoutine:
ldr r0,=myVar1; Load the address of _myVar
ldr r1,=0x12345678
str r1,[r0]
ldr r0,=myVar1; Load the address of _myVar
ldr r1,=0x87654321
str r1,[r0]
bx lr
which would get translated into:
ldr r0,dat1
ldr r1,dat2
str r1,[r0]
ldr r0,dat3
ldr r1,dat4
str r1,[r0]
bx lr
... followed some time later by
dat1 dcd _myVar1
dat2 dcd 0x12345678
dat3 dcd _myVar2
dat4 dcd 0x12345678
or perhaps even something like:
mar r0,dat1
ldrm r0,[r1,r2,r3,r4]
str r2,[r1]
str r4,[r3]
bx lr
... followed some time later by
dat1 dcd _myVar1
dat2 dcd 0x12345678
dat3 dcd _myVar2
dat4 dcd 0x12345678
请注意,_myVar 和 0x12345678 可以紧跟在它们出现的例程代码之后;如果您尝试使用最后一条指令后面的标签来确定例程的长度,则该长度将无法包含补充数据。
ARM 需要注意的另一件事是,由于历史原因,即使代码实际上从半字边界开始,代码地址通常也会设置其最低有效位。因此,地址为 0x12345679 的指令将占用从 0x12345678 开始的两个或四个字节。这会使 memcpy 之类的地址计算复杂化。
我的建议是用汇编语言编写一个小程序来做你需要的事情。这只是几条指令,您可以确切地知道代码在做什么以及它可能具有的地址依赖关系,并且您不必担心未来的编译器版本会以这样的方式更改您的代码以破坏某些东西[例如第三个由于 M3 的 LDR 指令可以处理未对齐的读取,因此即使位于奇数半字边界上,上述代码的版本也没有问题dat1
,但使用 LDRM 的第四个版本(稍微更快和更紧凑)在这种情况下会失败;即使今天的编译器版本使用四个 LDR 指令,未来的版本也可能使用 LDRM]。