对于外部指针声明,ARM代码显然无法正常工作

时间:2014-03-20 18:33:50

标签: c gcc assembly arm extern

我正在为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;是在内存中分配的字节变量的地址,因此将其声明为向量是没有意义的。

感谢您的帮助。

2 个答案:

答案 0 :(得分:3)

考虑数组和指针之间的区别:

  • 数组是没有 l-value 的符号。因此,它不能在运行时更改。
  • 指针是 具有 l值的符号。因此,可以在运行时更改。

如果编译器&#34;认为&#34;变量a在运行时可能会发生变化,然后它必须添加一个代码,用于在尝试从其指向的内存地址加载值之前从内存加载其值。

如果编译器&#34;知道&#34;变量a在运行时期间永远不会更改,然后它可以添加一个代码,用于直接从它指向的(常量)内存地址加载值。

顺便说一句,虽然您的代码可以编译和链接而没有错误,但由于变量a的模糊声明,我不确定它在运行时是否会崩溃,所以我建议你只需声明uint8_t a[1]

答案 1 :(得分:2)

以下是ab在内存中的样子:

     +------------------------------------------+
   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未初始化,因此可能无法指向有效的内存位置。

链接/加载后,ab的地址将为人所知。必须将这些地址加载到寄存器中才能访问变量所在的内存。在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

最后,我们从子程序返回。