【问题标题】:How does ARM processors differentiate between negative numbers and their positive equivalent?ARM 处理器如何区分负数和正数?
【发布时间】:2020-01-15 01:00:48
【问题描述】:

让我们考虑一个 8 位处理器来简化我的问题。我知道 -2 存储为它的 2 的补码,即 0b1111_1110,这个数据块的十进制表示是 254,对吗?现在,我的问题是 ARM 处理器如何区分“-2”和“254”,因为它们的二进制表示是相同的?

我尝试查找整个互联网,每个人都在解释处理器如何存储负数的方式。我只需要知道它们是如何区分的。

【问题讨论】:

  • 你需要知道2的恭维,这就是cpu用来表示有符号数的方式。
  • 下面有很多很好的答案。有符号与无符号是程序员关心的,而不是处理器(ARM 与这里无关,每个处理器都有处理这个问题的方法)。加减法不受影响,他们无法区分有符号和无符号,二进制补码的美丽。由于符号扩展,乘法关心。 N * N 位结果的低 N 位不受影响,但由于需要 2*N 位来完全存储该结果,结果的高 N 位不同。所以你需要一个有符号和无符号的乘法,或者用另一种方式解决它,你可以。除法类似。
  • 如果您需要我们中的一个可以展示这种铅笔和纸样式,长除法,长乘法,您可以在其中看到所有的位。一旦你看到了,你就可以将它应用到每个单独的指令集。 ARM、x86 等

标签: c arm embedded processor firmware


【解决方案1】:

-2 存储为它的 2 的补码,即 0b1111_1110,这块数据的十进制表示是 254,对吗?

是的,对于典型的现代系统来说确实如此。

现在,我的问题是 ARM 处理器如何区分“-2”和“254”,因为它们的二进制表示是相同的?

处理器没有;编译器会。

假设你有表达式value > 0。变量value 和常量0 都有一个类型。根据这些类型,编译器选择要使用的 CPU 指令。因此有符号和无符号比较可能会导致不同的编译器输出。

处理器不知道代码中的类型。它只是执行这些选定的指令。

Example with ARM64 gcc:

int icmp(int num) {
    return num > 0;
}

int ucmp(unsigned int num) {
    return num > 0;
}
icmp:
        sub     sp, sp, #16
        str     w0, [sp, 12]
        ldr     w0, [sp, 12]
        cmp     w0, 0
        cset    w0, gt
        and     w0, w0, 255
        add     sp, sp, 16
        ret
ucmp:
        sub     sp, sp, #16
        str     w0, [sp, 12]
        ldr     w0, [sp, 12]
        cmp     w0, 0
        cset    w0, ne
        and     w0, w0, 255
        add     sp, sp, 16
        ret

查看编译器如何生成略有不同的cset 指令。

【讨论】:

    【解决方案2】:

    大多数处理器,包括 Arm 处理器,不区分有符号数和无符号数。包含 0b1111_1110 的字节可以解释为值为 254 的无符号整数或值为 -2 的有符号整数。或者可以解释为其他东西,比如浮点数、定点数、字符等。决定这种解释的是你对它进行的操作。

    对于许多指令,值是有符号整数还是无符号整数并不重要:有符号整数的表示旨在通过对字长取模来使其轻量级。例如,将两个相同大小的值相加只是一个add 指令;值是否有符号无关紧要。

    对于某些指令,处理器提供不同的指令。比如instructions to copy a value to a larger register有两组:SXTB(Sign Extend Byte)和friends,以及UXTB(Zero Extend Byte)和friends。 UXT* 指令将一个值复制到目标寄存器的低位,并将高位设置为零。 SXT* 指令将一个值复制到目标寄存器的低位,并将高位设置为该值的高位,即将这个高位解释为符号位。

    从 C 的角度来看,编译器的工作是根据操作数使用正确的指令。例如,如果编译器看到

    uint8_t x = 0xfe;
    uint32_t y = x + 3;
    

    它决定最好的编译方法是将x存储在32位寄存器的低位,y作为另一个32位寄存器,它将发出一个UXTB指令来设置y0x000000fe 的寄存器,然后是 ADD 指令以获得所需的 x 值。 (当然在实践中这个 sn-p 会被优化掉。)

    【讨论】:

      【解决方案3】:

      CPU 不知道存储在特定内存位置的数据类型 - 数据类型仅存在于编程语言中。由编译器和/或程序员来跟踪存储在某个位置的类型。在 C 编程时,大部分时间编译器都会为您完成这项工作。

      当我们说“CPU 是 2 的补码”时,我们指的是有符号算术指令的行为。也就是说,当您运行执行0 - 1 的 CPU 指令时,该指令将产生二进制数 1111 .... 1111b,以及在条件代码寄存器中设置的相应标志,表示否定结果。

      程序员可以使用或选择忽略“否定标志”——在这种情况下,机器代码已经执行了明确定义的下溢。然而,遗憾的是,C 编程中不存在明确定义的有符号数上溢/下溢的概念。因此,如果我们在 C 中产生上溢/下溢,编译器可能会生成不正确的代码。如果我们在汇编程序中这样做,这将永远不会发生,因为行为在 CPU 级别上是明确定义的。

      【讨论】:

        【解决方案4】:

        2 的补码表示具有很好的特性,即加法和减法不必关心数字是有符号还是无符号,只要没有溢出即可。 0b11111110+0b00000001 给出0b11111111,当解释为有符号值时为-2 + 1 = -1,或当解释为无符号时为254 + 1 = 255

        当有符号很重要时,有符号和无符号的机器代码指令不同,例如有符号和无符号乘法的SMULLUMULL。通过使用不同的condition code suffixes 来比较值,即在比较之后的指令中检查另一组标志是否有符号和无符号类型,例如BLE 用于签名 <=BLS 用于未签名 <=

        【讨论】:

          【解决方案5】:

          这取决于变量的声明方式。

          对于 8 位变量,您有标准类型 uint8_tsint8_t 分别用于有符号或无符号。

          例如

          #include <stdint.h>
          
          uint8_t a = 254;
          sint8_t b = -2;
          

          如果你正在尝试这个,你还应该研究Integer promotion rules

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2014-01-23
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2017-01-04
            • 1970-01-01
            相关资源
            最近更新 更多