【问题标题】:ARM code apparently is not working properly for an external pointer declaration对于外部指针声明,ARM 代码显然不能正常工作
【发布时间】:2014-03-20 18:33:49
【问题描述】:

我正在将代码编译到 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得到a的地址,r3得到b的地址。

这不是我想要的。 为了让 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得到b的地址,r1得到a的地址。

注意:我做了一个动态链接来输入正确的“a”和“b”值。所以在代码的开头 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 

有人知道为什么第一种方法不能正常工作吗?

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

感谢您的帮助。

【问题讨论】:

  • 也许阅读 this 会有所帮助。
  • 变量为extern 并没有什么神奇之处(除了您不需要指定数组中元素的数量)。如果您删除了externs,并为数组指定了大小,您将(应该)看到相同的代码。
  • extern 加入其中是因为它提供了一个机会,可以意外地以略微不兼容的方式重新声明其他地方定义的内容。在数组被正确声明为数组的文件中,您可以使用指针表示法访问它 - 这里的问题是它被声明了两次,一次(在另一个文件中)作为数组,一次(在这个文件中)作为简单的指针,那些不兼容实现。
  • 没有看到实际的声明,我无法确定它是否存在微妙的不兼容。我假设extern 的类型与声明的类型匹配。
  • 问题在于,在 C 语言中,人们倾向于混淆数组和指针,因为在大多数情况下,数组的行为就好像它们是指向第一个元素的指针。当你在封面下看时,你会发现它们实际上是不同的。如果你将一个变量声明为一个数组,然后告诉另一个编译单元它是一个指针(使用extern),那么代码将编译和链接,但它不会正确运行,因为两个编译单元会查看以两种完全不同的方式使用同一块内存。

标签: c gcc assembly arm extern


【解决方案1】:

想想数组和指针的区别:

  • 数组是没有左值的符号。因此,它在运行时无法更改。
  • 指针是确实具有左值的符号。因此,它可以在运行时更改。

如果编译器“认为”变量a 可能在运行时发生变化,那么它必须添加一个代码以从内存中加载其值,然后再尝试从它所指向的内存地址加载值。

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

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

【讨论】:

  • 对不起,“变量 a 在内存中分配为 uint8_t”我的意思是 a 对应于字节变量的地址。但是第一种情况下的“程序集”,通过执行“ldr r1,[r2,#0]”和“strb r0,[r1,#0]”,几乎就像双指针一样充当“a”。这不正确,不是吗?
  • 好的,你改变了描述,所以我删除了纠正你的行(在答案的开头)......我认为第一种情况下的程序集好像需要执行两个加载操作而不是一个,两个存储操作而不是一个。
  • extern type array[] 是您必须声明从链接器文件创建的外部的典型方式。
【解决方案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

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

【讨论】:

  • 我相信以下指令会:“ldr r1, [r2, #0]” r1 = *a(r1 包含垃圾)“strb r0, [r1, #0]” *r1 = r0 ( *(*a) = r0 ) 是这个,不是吗?
  • 不,他们有 r1 = a 和 *a = r0。 pc-relative 的ldr 指令集将a 和b 的地址 加载到寄存器中。 r2-relative ldr 指令正在加载由a 的地址确定的内存位置;因此,a 的值。
  • r2 包含 &amp;a,r3 包含 &amp;b[0](&amp;a 和 &amp;b[0] 在偏移量 +0x14 和 +0x18 处从 @987654377 隐藏为 32 位数据值@ 分别)。 ldr r1, [r2, #0] 将a 的值加载到r1 中,因此r1 包含浮动的uint8_t 右侧的地址。
  • 谢谢!我把 a 的值放在“[pc, #16]”上,正确的是 &a。非常感谢。我在想编译器将指针和向量视为相等,但没有。
猜你喜欢
  • 2018-07-30
  • 2011-01-17
  • 1970-01-01
  • 2019-03-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-04-04
  • 1970-01-01
相关资源
最近更新 更多