模拟jg指令(datalab的isGreater)

use*_*613 5 c assembly instructions eflags

我正在做CSAPP的datalab,即isGreater功能.
这是描述

isGreater - if x > y  then return 1, else return 0
   Example: isGreater(4,5) = 0, isGreater(5,4) = 1
   Legal ops: ! ~ & ^ | + << >>
   Max ops: 24
   Rating: 3
Run Code Online (Sandbox Code Playgroud)

x和y都是int类型.
所以,我认为模拟JG指令,以实现it.Here是我的代码

int isGreater(int x, int y)
{
    int yComplement = ~y + 1;
    int minusResult = x + yComplement;  // 0xffffffff
    int SF = (minusResult >> 31) & 0x1; // 1
    int ZF = !minusResult; // 0
    int xSign = (x >> 31) & 0x1; // 0 
    int ySign = (yComplement >> 31) & 0x1; // 1
    int OF = !(xSign ^ ySign) & (xSign ^ SF); // 0
    return !(OF ^ SF) & !ZF;
}
Run Code Online (Sandbox Code Playgroud)

jg指令需要SF == OF和ZF == 0.
但它不能通过特殊情况,即x = 0x7fffffff(INT_MAX),y = 0x80000000(INT_MIN).
我推断它是这样的:
x + yComplement = 0xffffffff,所以SF = 1,ZF = 0,因为xSign!= ySign,OF设置为0.
那么,我的代码有什么问题,我的OF设置操作错了吗?

Pet*_*des 4

您在加法中检测到溢出x + yComplement,而不是在整体减法中检测到溢出

-INT_MIN其本身在 2 的补码中溢出;INT_MIN == -INT_MIN。这是2 的补码异常1

您应该对任何负数(除了INT_MIN) minus进行快速正溢出检测INT_MIN。结果加法将有符号溢出。例如-10 + INT_MIN溢出。

http://teaching.idallen.com/dat2343/10f/notes/040_overflow.txt有一个用于加法和减法的输入/输出符号表。溢出的情况是输入符号相反但结果符号匹配y

      SUBTRACTION SIGN BITS  (for num1 - num2 = sum)
    num1sign num2sign sumsign
   ---------------------------
        0 0 0
        0 0 1
        0 1 0
 *OVER* 0 1 1 (subtracting a negative is the same as adding a positive)
 *OVER* 1 0 0 (subtracting a positive is the same as adding a negative)
        1 0 1
        1 1 0
        1 1 1
Run Code Online (Sandbox Code Playgroud)

您可以直接将其与原始的x和一起使用y,并且仅用yComplement作获取 的一部分minusResult。调整你的逻辑以匹配这个真值表。

或者您可以使用int ySign = (~y) >> 31;其余代码并保持不变。(使用 tmp 来保持~y,这样您只需执行一次操作,对于 this 和yComplement)。1 的补码逆 ( ~) 不会受到 2 补码异常的影响。


脚注 1:符号/数值和补码有两种冗余方式来表示 0,而不是没有倒数的值。

有趣的事实:如果你创建一个整数绝对值函数,你应该考虑结果unsigned以避免这个问题。 int不能代表 的绝对值INT_MIN


效率提升:

如果使用unsigned int,则不需要& 1在移位后进行,因为逻辑移位不会进行符号扩展。(作为奖励,它可以避免 C 签名溢出未定义行为+http://blog.llvm.org/2011/05/what-every-c-programmer-should-know.html)。

然后(如果您使用uint32_t, 或sizeof(unsigned) * CHAR_BIT代替 31)您将获得 2 的补码比较的安全且可移植的实现。(负数的有符号移位语义是在 C 中实现定义的。)我认为您正在使用 C 作为位操作的一种伪代码,并且对实际编写可移植实现不感兴趣,这很好。您的操作方式将在普通 CPU 上的普通编译器上运行。

或者您可以使用& 0x80000000将高位保留在适当的位置(但随后您必须左移!结果)。

这只是实验室的限制,你不能使用无符号或任何大于 0xff(255) 的常量

好的,所以您无权访问逻辑右移。不过,您最多需要一个&1。如果您只关心低位,但其余部分都是垃圾,那么处理数字是可以的。

你最终会这样做& !ZF,或者&0&1 . Thus, any high garbage in OF` 被擦除。

您还可以延迟>> 31直到将两个数字异或在一起之后。


这是一个有趣的问题,我想自己优化:

// untested, 13 operations
int isGreater_optimized(int x, int y)
{
    int not_y = ~y;
    
    int minus_y = not_y + 1;
    int sum = x + minus_y;

    int x_vs_y = x ^ y;       // high bit = 1 if they were opposite signs: OF is possible
    int x_vs_sum = x ^ sum;   // high bit = 1 if they were opposite signs: OF is possible

    int OF = (x_vs_y & x_vs_sum) >> 31;   // high bits hold garbage
    int SF = sum >> 31;

    int non_zero = !!sum;              // 0 or 1
    return (~(OF ^ SF)) & non_zero;      // high garbage is nuked by `& 1`
}
Run Code Online (Sandbox Code Playgroud)

请注意使用~代替 来!反转具有大量垃圾的值。

看起来将 OF 与 SF 分开计算仍然存在一些冗余,但实际上两次 sum 的异或并没有抵消。 x ^ sum是 的输入&,然后我们与 sum 进行异或。

不过,我们甚至可以推迟移位,而且我通过避免额外的反转发现了更多的优化。 这是 11 次操作

// replace 31 with  sizeof(int) * CHAR_BIT  if you want.  #include <limit.h>
// or use int32_t

int isGreater_optimized2(int x, int y)
{
    int not_y = ~y;

    int minus_y = not_y + 1;
    int sum = x + minus_y;
    int SF = sum;             // value in the high bit, rest are garbage

    int x_vs_y = x ^ y;       // high bit = 1 if they were opposite signs: OF is possible
    int x_vs_sum = x ^ sum;   // high bit = 1 if they were opposite signs: OF is possible
    int OF = x_vs_y & x_vs_sum;   // low bits hold garbage

    int less = (OF ^ SF);
    int ZF   = !sum;               // 0 or 1
    int le   = (less >> 31) & ZF;  // clears high garbage
    return  !le;                   // jg == jnle
}
Run Code Online (Sandbox Code Playgroud)

我想知道是否有编译器可以通过这个手动比较并将其优化为cmp edi, esi/ setg al,但没有这样的运气:/我猜这不是他们寻找的模式,因为本来可以编写的代码往往会x > y这样编写:P

但无论如何,这里是Godbolt 编译器资源管理器上 gcc 和 clang 的 x86 asm 输出。