【问题标题】:Negative hexadecimal representation负十六进制表示
【发布时间】:2021-07-12 00:57:50
【问题描述】:

我有一个问题是这样的:

 mov r15, 0x407116EF3867BBCA  
 sub r15, 0x95F67F70A1BCEE9D

这段代码执行后 r15 的值是多少?
(注意整数上溢/下溢)

解决方案: 用于调试的 python 3 数学字符串:(i64 - i64_2) % (2**64) 当我按照这个公式解决了这个问题时,我得到了 0xaa7a977e96aacd2d

我怀疑答案应该是否定的,但我认为它已转换为 2 的补码。但是如果是这样的话,有什么必要把它转换成 2 的补码呢?那么,我的想法是否正确?如果不是,请纠正我。另外,这个公式背后的逻辑是什么(取模2 ^ 64)。

【问题讨论】:

  • 你得到了和我一样的答案。 Python 有无限长的整数。模 2^64 是将结果截断为您拥有的 64 位寄存器。
  • @timroberts 不,我不明白的是,指令将作为“r15=r15-1”执行,即 (0x407116EF3867BBCA - 0x95F67F70A1BCEE9D) 所以我们从 a降低一个,所以答案应该是肯定的?但我们得到了一个积极的结果。
  • Python % 是模数(总是正数),而不是 x86 idiv 指令后的 RDX 之类的余数。无论如何,我不会使用%。按位与& 0xFFFFFFFFFFFFFFFF 之类的操作对于您要执行的操作似乎更直观(按位截断为 64 位)。
  • 另外,如果这应该是 x86-64,那将无法组装:只有 mov 可以使用 64 位立即数,而 0x95F67F70A1BCEE9D 不适合 / is' t 可表示为 32 位符号扩展立即数。
  • 0x95F67F70A1BCEE9D 为负数(设置了高位)。所以,实际上,你是在取正值减去负值,但无论哪种方式都会导致溢出。

标签: python assembly hex integer-overflow twos-complement


【解决方案1】:

是的,在汇编中,一个 64 位的寄存器值总是可以用 16 个十六进制数字表示。没有单独的加/减位; 最好将 add / sub 之类的二进制操作视为无符号1,只需担心2's complement 对最终结果的解释2 .


这意味着使用任意精度计算器(如 Python 3 整数或 calc aka apcalc)有点不方便,当减法下溢超过 0 时,它会显示负结果,而不是像 C uint64_t 那样包装。

有两种方法可以考虑将结果固定为您想要的(在[0 .. 2**64-1] 范围内):

  • Modulo 将其缩小到该范围。 这就是 Python 中的 % 所做的。(与 C 等其他一些语言或 x86-64 asm 中的 idiv 指令不同,Python % 为您提供模数,始终为正数,而不是余数。例如-1 % 21,但在C 中对于有符号整数它是-1

    您甚至可以手动通过将2**64 添加到负数来进行该缩减,以获得2 的补码二进制表示。因为您知道任何加法或减法结果都将不小于该范围之外的2**64,因此只需进行一次加法(或减法以进行进位加法)。

  • 按位将其截断为 64 位,采用 Python 内部扩展精度 2 的补码表示的低 64 位。这取决于 Python 在内部使用 2 的补码,这可能是有保证的,并且在实践中肯定有效(大概至少当 Python 在任何本身使用 2 的补码的正常系统上运行时。)

所有三个都给出相同的正确结果。在交互式 Python 3.9 会话中:

>>> (0x407116EF3867BBCA - 0x95F67F70A1BCEE9D)
-6162446570153652947
>>> hex (0x407116EF3867BBCA - 0x95F67F70A1BCEE9D)
'-0x55856881695532d3'

>>> hex( (0x407116EF3867BBCA - 0x95F67F70A1BCEE9D) % (2**64) )
'0xaa7a977e96aacd2d'
>>> hex( (0x407116EF3867BBCA - 0x95F67F70A1BCEE9D) + 2**64 )
'0xaa7a977e96aacd2d'

>>> hex( (0x407116EF3867BBCA - 0x95F67F70A1BCEE9D) & (2**64-1) )
'0xaa7a977e96aacd2d'

另外,如果这应该是 x86-64,sub 将无法组装:只有 mov 可以使用 64 位立即数,而 0x95F67F70A1BCEE9D 不能适合(不能表示为)32 位符号扩展立即数。

但如果是这样,那么 CF 将被设置,因为从高位借位(因为 0x4... - 0x9... = 0xa... 超过零:减法的左侧操作数是右侧下方无符号)。

并且 OF 将被设置,因为在有符号解释中(我们将 MSB 视为具有- 2^63 而不是+ 2^63 的位值),正数减去更大的负数会产生这样的一个很大的阳性结果,它溢出到阴性。 (即正 - 负 = 负意味着有符号溢出,就像正 + 正 = 负一样)

根据结果的MSB,SF=1。


脚注 1
加宽乘法和除法的高半部分通常关心 MSB 的位值; add/sub don't: 2 的补码加法与无符号的二进制操作相同,包括回绕。这就是为什么 x86-64 只有一条 sub 指令,但有 dividiv,并且对于单操作数扩展乘法有 mul 与通常的 imul 分开。但是像 imul eax, r9d 这样的 imul 的非扩展形式对于有符号或无符号是相同的。

脚注 2
如果设置了高位,那么如果您将位模式解释为 2 的补码有符号整数而不是无符号整数,则它是负数。见Wikipedia's article about 2's complement。如果第一个十六进制数字是 8 到 F,则设置高位,因此在您的情况下,0x9...0xa... 表示负数,而 0x4... 表示正数。

【讨论】:

  • 现在我明白了,非常感谢。
  • @heartbeat:干杯。如果这回答了您的问题,您可以使用投票箭头下方的复选框将其标记为“已接受”。
  • 一些汇编程序(例如 nasm)可能会接受带有警告的 sub 并截断常量,使其成为 sub r15,0xffffffffa1bcee9d。可以想象,这可能是一个关于该案例的棘手问题......
  • @NateEldredge:哦,对了,NASM 只是警告和截断而不是在那里出错。尽管如此,在没有确认的情况下,这实际上甚至应该是 x86-64,而不是一些玩具 64 位 ISA,我将把额外的讨论留给 cmets。答案中的 x86-64 特定内容可以很好地作为其他 ISA 的示例(尽管也许我应该提到一些 ISA 设置了 carry = !borrow for sub,与 x86 相反)
猜你喜欢
  • 2011-10-02
  • 2016-02-06
  • 2014-11-30
  • 2018-04-23
  • 1970-01-01
  • 2021-08-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多