当前位置: 首页 > 知识库问答 >
问题:

GCC生成的ARM和x86汇编代码的差异

卓嘉良
2023-03-14

让我们用一个简单的C代码来设置寄存器:

int main()
{
    int *a = (int*)111111;
    *a = 0x1000;
    return 0;
}

当我使用1级优化为ARM(ARM none eabi gcc)编译此代码时,汇编代码如下所示:

mov     r2, #4096
mov     r3, #110592
str     r2, [r3, #519]
mov     r0, #0
bx      lr

看起来地址111111被解析到最接近的4K边界(110592)并移动到r3,然后通过将519添加到110592 (=111111)来存储值4096(0x1000)。为什么会这样?

在x86中,组装非常简单:

movl    $4096, 111111
movl    $0, %eax
ret

共有3个答案

郎宏浚
2023-03-14

编译器可能正在利用ARM即时值编码来减小代码大小。基本上110592是0x1B

穆修杰
2023-03-14

地址必须分成两部分,因为这个特定常量不能用一条指令加载到寄存器中。

ARM文档规定了某些指令(如MOV)中允许的立即数常量的限制:

在ARM指令中,常量可以具有任何值,该值可以通过将8位值右旋转32位字中的任何偶数位来产生。

在32位Thumb-2指令中,常量可以是:

通过将一个8位值左移32位字中的任意位数而产生的任何常量。

0x00XY00XY形式的任何常数<0xXY00XY00形式的任何常数<0xXYXYXYXY形式的任何常数。

值111111(十六进制中的1B207)不能表示为上述任何值,因此编译器必须将其拆分。

110592是1B000,因此它满足第一个条件(8位值0x1B向左旋转12位),可以使用MOV指令加载。

另一方面,STR指令对使用的偏移量有一组不同的限制。特别是,519(0x207)属于ARM模式下单词存储/加载允许的-4095到4095范围。

在这种特定情况下,编译器仅将常量拆分为两部分。如果您的立即数有更多的位,它可能需要生成更多的指令,或者使用文字池加载。例如,如果我使用0xABCDEF78,我会得到以下结果(对于ARMv7):

movw    r3, #61439
movt    r3, 43981
mov     r2, #4096
str     r2, [r3, #-135]
mov     r0, #0
bx      lr

对于没有MOVW/MOVT的架构(例如ARMv4),GCC似乎回到了文字池:

    mov     r2, #4096
    ldr     r3, .L2
    str     r2, [r3, #-135]
    mov     r0, #0
    bx      lr
.L3:
    .align  2
.L2:
    .word   -1412567041
狄侯林
2023-03-14

这种编码背后的原因是,x86具有可变大小的指令——从1字节到16字节(甚至可能有更多前缀)。

ARM指令的宽度为32位(不包括Thumb模式),这意味着不可能在单个操作码中编码所有32位宽度的常量(立即)。

固定大小的体系结构通常使用几种方法加载大型常量:

1)  movi  #r1, Imm8  ; // Here Imm8 or ImmX is simply X least significant bits
2)  movhi #r1, Imm16 ; // Here Imm16 loads the 16 MSB of the register
3)  load  #r1, (PC + ImmX);  // use PC-relative address to put constant in code
4)  movn  #r1, Imm8 ;  // load the inverse of Imm8 (for signed constants) 
5)  mov(i/n) #1, Imm8 << N;       // where N=0,8,16,24

可变大小的体系结构OTOH可以将所有常量放在一条指令中:

xx xx xx 00 10 00 00 11 11 11 00 ; // assuming that it takes 3 bytes to encode
                                 ; // the instruction and the addressing mode
; added with 4 bytes to encode the 4096 and 4 bytes to encode 0x00111111
 类似资料:
  • 我正在为64位mips机器使用gcc编译器。我注意到生成的一段汇编代码很有趣。下面是详细信息: 通常,bnez将立即跳到0xb0。但在0xb0之后的块中,我确信程序必须使用a1作为参数。但是我们可以看到,在0xb0之后,a1从未出现在块中。 但是a1在0x58中使用,就在bnez(0x54)之后。 那么0x54和0x58指令有可能同时执行吗?超标量处理器通过同时将多条指令分派到处理器上的冗余功能单

  • 至少在GCC中,如果我们提供生成汇编代码的选项,编译器会通过创建一个包含汇编代码的文件来服从。但是,当我们简单地运行命令而没有任何选项时,它不会在内部生成汇编代码吗? 如果是,那么为什么它需要首先生成一个汇编代码,然后将其翻译成机器语言?

  • 我试图翻译成x86汇编,以帮助我更好地理解在x86汇编中编码的概念,我觉得自己被困在了如何开始编写这段代码上。

  • 问题内容: 我想分解一下我拥有的可引导x86磁盘的MBR(前512个字节)。我已将MBR复制到使用 对可以反汇编文件的Linux实用程序有何建议? 问题答案: 您可以使用objdump。根据本文的语法为:

  • 我还想知道是否会有更直接的方法来编译和运行生成的代码。

  • 因此,通常关于通过汇编代码提高性能的问题的答案是“不要打扰,编译器比你聪明”。我明白了。 但是,我注意到优化的线性代数库(例如ACML)可以比标准编译库实现2到5倍的性能改进。例如,在我的8核机器上,与现有的单线程BLAS实现相比,优化的矩阵乘法运行速度快了30倍以上,这意味着,在考虑了由于使用所有内核而提高的8倍之后,仅仅通过优化仍然可以提高4倍。 所以在我看来,优化的汇编代码确实可以带来巨大的