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

C 对于外部指针声明,ARM代码显然无法正常工作,c,gcc,assembly,arm,extern,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; } 并生成以下asm: 00000000 <teste>: 0: 4a04 ldr r2, [pc, #16] ; (14 <teste+

我正在编译一段ARM代码,生成的程序集不是我所期望的

以下代码:

#include <stdint.h>

extern uint8_t* a;
extern uint8_t b[];

void teste(void)
{
    *a = b[1];
    b[2] = *a;
}
并生成以下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
         ...
 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
         ...
有人知道为什么第一种方法不能正常工作吗

“a”是内存中分配的字节变量的地址,因此将其声明为向量没有意义


谢谢您的帮助。

想想数组和指针之间的区别:

  • 数组是没有l值的符号。因此,它不能在运行时更改
  • 指针是一种符号,不具有l值。因此,它可以在运行时改变
如果编译器“认为”变量
a
在运行时可能会更改,那么它必须添加一个代码,以便从内存加载其值,然后再尝试从它所指向的内存地址加载该值

如果编译器“知道”变量
a
在运行时从不更改,那么它可以添加一个代码,直接从它所指向的(常量)内存地址加载该值


顺便说一句,虽然您的代码可以编译和链接而不会出错,但我不确定它在运行时不会因为变量
a
的声明不明确而崩溃,因此我建议您只需声明
uint8\u t a[1]

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

  • 数组是没有l值的符号。因此,它不能在运行时更改
  • 指针是一种符号,不具有l值。因此,它可以在运行时改变
如果编译器“认为”变量
a
在运行时可能会更改,那么它必须添加一个代码,以便从内存加载其值,然后再尝试从它所指向的内存地址加载该值

如果编译器“知道”变量
a
在运行时从不更改,那么它可以添加一个代码,直接从它所指向的(常量)内存地址加载该值


顺便说一句,尽管您的代码可以编译和链接而不会出错,但我不确定它在运行时不会因为变量
a
的声明不明确而崩溃,因此我建议您只需声明
uint8\u t a[1]

以下是
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>)
此行正在将
b[0]
的地址(存储在子例程之后的代码中)加载到
r3

    4:   6811            ldr     r1, [r2, #0]
此行正在将存储在
a
中的指针加载到
r1
中。因此,
r1
现在指向一些
uint8\t
(图中右侧浮动的一个)

此行将一个字节从地址
r3+1
(又称
b[1]
)加载到
r0

    8:   7008            strb    r0, [r1, #0]
这将存储我们刚刚加载到地址
r1+0
(又称
*a
)中的字节

此行将
a
重新加载到
r2
中。这是不必要的,因为我们在
r1
中已经有了这个值;但是,我猜您已经禁用了优化

    c:   7812            ldrb    r2, [r2, #0]
此行将一个字节从地址
r2+0
(又称
*a
)加载到
r2

    2:   4b05            ldr     r3, [pc, #20]   ; (18 <teste+0x18>)
    e:   709a            strb    r2, [r3, #2]
此行存储我们刚刚加载到地址
r3+2
(aka
b[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>)
此行正在将
b[0]
的地址(存储在子例程之后的代码中)加载到
r3

    4:   6811            ldr     r1, [r2, #0]
此行正在将存储在
a
中的指针加载到
r1
中。因此,
r1
现在指向一些
uint8\t
(图中右侧浮动的一个)

此行将一个字节从地址
r3+1
(又称
b[1]
)加载到
r0

    8:   7008            strb    r0, [r1, #0]
本街