12

当我查看用 C/C++ 编写的代码的反汇编时,我总是觉得这很令人困惑。

有一个具有某些值的寄存器。我想知道它是代表有符号数还是无符号数。我怎样才能找到这个?

我的理解是,如果它是一个有符号整数,如果它是负数,则设置 MSB,如果它是正数,则不设置。如果我发现它是一个无符号整数,则 MSB 无关紧要。这个对吗?

无论如何,这似乎无济于事:我仍然需要确定整数是否已签名,然后才能使用此信息。如何才能做到这一点?

4

4 回答 4

9

你最好的选择也是寻找比较和相关的动作/标志使用,比如一个分支。根据类型,编译器将生成不同的代码。由于大多数(相关)架构提供标志来处理有符号值。以 x86 为例:

jg, jge, jl, jle = branch based on a signed comparison (They check for the SF flag)
ja, jae, jb, jbe = branch based on a unsigned comparison (They check for the CF flag)

CPU 上的大多数指令对于有符号/无符号操作都是相同的,因为这些天我们使用的是二进制补码表示。但也有例外。

让我们以右移为例。对于 X86 上的无符号值,您将使用 SHR 将某些内容向右移动。这将在左侧的每个“新创建的位”上添加零。

但是对于有符号值,通常会使用 SAR,因为它将 MSB 扩展到所有新位。这就是所谓的“符号扩展”,并且再次只有效,因为我们使用的是二进制补码。

最后但同样重要的是,有符号/无符号乘法/除法有不同的指令。

idiv or one-operand imul = signed
div or mul/mulx = unsigned

正如评论中所指出的,imul有 2 个或 3 个操作数并不意味着什么,因为像加法一样,非扩展乘法对于有符号和无符号是相同的。仅以imul一种不会浪费时间编写高半结果的形式存在,因此编译器(和人类)不管有符号性如何都使用imul,除非他们特别想要高半结果,例如优化uint64_t = u32 * (uint64_t)u32。唯一的区别在于设置的标志,这些标志很少被查看,尤其是编译器生成的代码。

此外, NEG 指令通常仅用于有符号值,因为它是二进制补码否定。(如果用作 的一部分abs(),结果可能被认为是无符号的,以避免在 INT_MIN 上溢出。)

于 2012-06-26T12:23:56.883 回答
4

一般来说,您将无法做到。整数值发生的许多事情对于有符号或无符号值的发生方式相同。以作业为例。唯一的判断方法是代码是否恰好在进行算术运算。您绝对无法通过查看价值来判断;无论哪种方式,所有可能的位模式都是有效的。

于 2012-06-26T10:43:18.813 回答
2

在大多数处理器中(至少那些使用二进制补码数学的处理器),存储在寄存器或内存中的整数没有固有的符号。解释取决于使用的说明。一个简短的总结:

  1. 对于有符号数和无符号数,加法和减法产生完全相同的位模式,因此通常没有带符号的加法或减法。(但是,MIPS 有单独的指令,如果操作溢出会导致陷阱)。

  2. 除法和乘法对于有符号数和无符号数确实会产生不同的结果,因此如果处理器支持它,它们会成对出现(x86:mul/imul,div/idiv)。

  3. 条件分支也可能根据比较结果的解释而有所不同(通常以减法实现)。例如,在 x86 上,有jgfor signed greaterjafor unsigned above

请注意,浮点数(至少 IEEE 格式)使用显式符号位,因此上述不适用于它们。

于 2012-06-26T13:52:43.387 回答
0

除了已经说过的内容之外,查看运行时值会有所帮助。

例如,在

add eax, edx    ; eax = 0xFFFFFFF0, edx = 100

eax可能包含一个有符号变量。这里没有保证,但在任何地方都没有保证——代码总是有可能是错误的。存在(有意或无意的)无符号溢出的代码,但它实际上更有可能被解释为有符号的非溢出。

于 2012-06-26T12:42:13.513 回答