我正在为ARM编译代码,生成的程序集不符合我的预期。
以下代码:
#include <stdint.h>
extern uint8_t* a;
extern uint8_t b[];
void teste(void)
{
*a = b[1];
b[2] = *a;
}
在ARM GCC 4.7.3上编译时,ARM GCC 4.8.3生成以下asm:
00000000 <teste>:
0: 4a04 ldr r2, [pc, #16] ; (14 <teste+0x14>)
2: 4b05 ldr r3, [pc, #20] ; (18 <teste+0x18>)
4: 6811 ldr r1, [r2, #0]
6: 7858 ldrb r0, [r3, #1]
8: 7008 strb r0, [r1, #0]
a: 6812 ldr r2, [r2, #0]
c: 7812 ldrb r2, [r2, #0]
e: 709a strb r2, [r3, #2]
10: 4770 bx lr
12: bf00 nop
...
Obs:r2获取&#34; a&#34;和&#34; b&#34;的r3地址。
这个asm不是我想要的。 为了让asm正常工作我必须做
extern uint8_t a[];
并生成以下asm:
00000000 <teste>:
0: 4a02 ldr r2, [pc, #8] ; (c <teste+0xc>)
2: 4903 ldr r1, [pc, #12] ; (10 <teste+0x10>)
4: 7853 ldrb r3, [r2, #1]
6: 7093 strb r3, [r2, #2]
8: 700b strb r3, [r1, #0]
a: 4770 bx lr
...
Obs:r2获取&#34; b&#34;的地址,&#34; a&#34;的r1地址。
注意:我进行了动态链接以输入&#34; a&#34;的正确值。和&#34; b&#34;。所以在代码r2和r3的开头(在第一个代码上)或r1和r2(在第二个代码上)得到正确的值。
编译我正在使用以下内容:
arm-none-eabi-gcc.exe -c code.c -o code.o -mthumb -mcpu=cortex-m4 -O2 -mlong-calls -mword-relocations -mabi=atpcs -mfloat-abi=soft -mcaller-super-interworking
arm-none-eabi-ld.exe -o code.elf code.o --relocatable --strip-all --discard-all --embedded-relocs
有谁知道为什么第一种方法不能正常工作?
&#34; a&#34;是在内存中分配的字节变量的地址,因此将其声明为向量是没有意义的。
感谢您的帮助。
答案 0 :(得分:3)
考虑数组和指针之间的区别:
如果编译器&#34;认为&#34;变量a
在运行时可能会发生变化,然后它必须添加一个代码,用于在尝试从其指向的内存地址加载值之前从内存加载其值。
如果编译器&#34;知道&#34;变量a
在运行时期间永远不会更改,然后它可以添加一个代码,用于直接从它指向的(常量)内存地址加载值。
a
的模糊声明,我不确定它在运行时是否会崩溃,所以我建议你只需声明uint8_t a[1]
。
答案 1 :(得分:2)
以下是a
和b
在内存中的样子:
+------------------------------------------+
a | uint8_t* >----------------------+
+------------------------------------------+ |
| +---------+
+---------+ +->| uint8_t | a[0] (or *a)
b[0] | uint8_t | +---------+
+---------+ | uint8_t | a[1] (or *(a+1))
b[1] | uint8_t | +---------+
+---------+ | ... |
b[2] | uint8_t |
+---------+
| ... |
请注意b[1]
和b[2]
(以及可能b[0]
)显示为逻辑上驻留的位置,即使没有分配实际存储空间也是如此。此外,a
未初始化,因此可能无法指向有效的内存位置。
链接/加载后,a
和b
的地址将为人所知。必须将这些地址加载到寄存器中才能访问变量所在的内存。在ARM中,地址作为数据值存储,并通过pc相对寻址访问:
+------------------------------------------+
teste+0 | |
| I n s t r u c t i o n s |
| |
+------------------------------------------+ +------------
teste+14 | uint8_t** >--------------------------> a | uint8_t * ...
+------------------------------------------+ +------------
teste+18 | uint8_t* >------------------------+
+------------------------------------------+ | +---------+
+-> b[0] | uint8_t |
+---------+
b[1] | uint8_t |
+---------+
b[2] | uint8_t |
+---------+
| ... |
通过将这些常量地址存储在指令的固定(小)偏移处,代码可以使用16位THUMB操作码将它们加载到寄存器中。相反,MIPS代码通常使用2个32位指令序列来完成相同的操作,方法是将指令中嵌入的16位立即数加载到目标寄存器的上半部分和下半部分。
现在让我们逐步完成说明。
0: 4a04 ldr r2, [pc, #16] ; (14 <teste+0x14>)
此行正在将a
的地址(在子程序之后的代码中存储)加载到r2
。
2: 4b05 ldr r3, [pc, #20] ; (18 <teste+0x18>)
此行正在将b[0]
的地址(在子程序之后的代码中存储)加载到r3
。
4: 6811 ldr r1, [r2, #0]
此行将a
中存储的指针加载到r1
。因此,r1
现在指向一些uint8_t
(图中右侧浮动的那个)。
6: 7858 ldrb r0, [r3, #1]
此行将地址r3+1
(又名b[1]
)中的一个字节加载到r0
。
8: 7008 strb r0, [r1, #0]
这会将我们刚刚加载的字节存储到地址r1+0
(又名*a
)。
a: 6812 ldr r2, [r2, #0]
此行会将a
重新加载到r2
。这是不必要的,因为我们已在r1
中拥有此值;但是,我猜你已经禁用了优化。
c: 7812 ldrb r2, [r2, #0]
此行将地址r2+0
(又名*a
)中的一个字节加载到r2
。
e: 709a strb r2, [r3, #2]
此行将我们刚刚加载的字节存储到地址r3+2
(又名b[2]
)。
10: 4770 bx lr
最后,我们从子程序返回。