减去并检测下溢,最有效的方法?(带GCC的x86/64)

减去并检测下溢,最有效的方法?(带GCC的x86/64),gcc,assembly,x86,underflow,carryflag,Gcc,Assembly,X86,Underflow,Carryflag,我正在使用GCC4.8.1编译C代码,我需要检测x86/64体系结构上的减法运算是否出现下溢。两者都没有签名。我知道在汇编中很容易,但我想知道我是否可以在C代码中完成它,并让GCC以某种方式优化它,因为我找不到它。这是一个非常常用的函数(或者是低级的,这是术语吗?),所以我需要它是有效的,但是GCC似乎太笨了,无法识别这个简单的操作?我尝试了很多方法在C中给它提示,但它总是使用两个寄存器,而不是一个子寄存器和一个条件跳转。老实说,看到如此愚蠢的代码被写了这么多次(函数被调用了很多次),我很恼火

我正在使用GCC4.8.1编译C代码,我需要检测x86/64体系结构上的减法运算是否出现下溢。两者都没有签名。我知道在汇编中很容易,但我想知道我是否可以在C代码中完成它,并让GCC以某种方式优化它,因为我找不到它。这是一个非常常用的函数(或者是低级的,这是术语吗?),所以我需要它是有效的,但是GCC似乎太笨了,无法识别这个简单的操作?我尝试了很多方法在C中给它提示,但它总是使用两个寄存器,而不是一个子寄存器和一个条件跳转。老实说,看到如此愚蠢的代码被写了这么多次(函数被调用了很多次),我很恼火

我在C语言中的最佳方法似乎是:

if((a-=b)+b < b) {
  // underflow here
}
不用说,上面这句话很愚蠢,但它需要的只是:

sub rcx, rdx
jc underflow
这是非常恼人的,因为GCC确实理解sub以这种方式修改标志,因为如果我将其键入“int”,它将生成上面的精确值,除非它使用带符号跳转的“js”,而不是进位,如果无符号值差足够大,设置高位,则进位无效。然而,它表明它知道影响这些标志的子指令

现在,也许我应该放弃让GCC适当地优化它,用内联汇编完成它,我对内联汇编没有任何问题。不幸的是,这需要“asm goto”,因为我需要一个条件跳转,而asm goto对于输出不是很有效,因为它是不稳定的

我试过一些东西,但我不知道它是否“安全”使用。由于某些原因,asm goto不能有输出。我不想让它把所有寄存器都刷新到内存中,这会扼杀我所做的全部工作,这就是效率。但是,如果我使用空的asm语句,并在其前后将输出设置为“a”变量,那么这会起作用吗?安全吗?这是我的宏:

#define subchk(a,b,g) { typeof(a) _a=a; \
  asm("":"+rm"(_a)::"cc"); \
  asm goto("sub %1,%0;jc %l2"::"r,m,r"(_a),"r,r,m"(b):"cc":g); \
  asm("":"+rm"(_a)::"cc"); }
然后像这样使用它:

subchk(a,b,underflow)
// normal code with no underflow
// ...

underflow:
  // underflow occured here
这有点难看,但它工作得很好。在我的测试场景中,它编译得很好,没有易失性开销(将寄存器刷新到内存),也没有产生任何不好的结果,而且它似乎可以正常工作,但是这只是一个有限的测试,我不可能在任何地方测试它,因为我说这个函数/宏使用了很多,所以我想知道是否有人有知识,上述结构是否有不安全之处

特别是,如果发生下溢,则不需要“a”的值,因此请记住,我的内联asm宏是否会出现任何副作用或不安全的情况?如果没有,我将毫无问题地使用它,直到他们优化编译器,这样我就可以在我猜的时候把它替换回来


请不要把这变成一场关于过早优化或其他问题的辩论,继续讨论这个问题,我完全知道这一点,所以谢谢您。

下面的汇编代码如何(您可以将其包装成GCC格式):


然后调用/内联汇编代码,并在生成的布尔值上分支。

我可能错过了一些明显的东西,但为什么这不好呢

extern void underflow(void) __attribute__((noreturn));
unsigned foo(unsigned a, unsigned b)
{
    unsigned r = a - b;
    if (r > a)
    {
        underflow();
    }
    return r;
}
我已经检查过了,gcc会根据您的需要对其进行优化:

foo:
    movl    %edi, %eax
    subl    %esi, %eax
    jb      .L6
    rep
    ret
.L6:
    pushq   %rax
    call    underflow

当然,你可以随心所欲地处理下溢,我这样做只是为了保持asm的简单。

你测试过这是否真的快了吗?现代x86微体系结构使用微代码,将单个汇编指令转换为更简单的微操作序列。其中一些还进行微操作融合,将一系列汇编指令转换为单个微操作。特别是
测试%reg,%reg;jcc目标
被融合,可能是因为全局处理器标志是性能的祸根。

如果
cmp%reg,%reg;jcc目标是mOp融合的,gcc可能会使用它来获得更快的代码。根据我的经验,gcc非常擅长调度和类似的低级优化。

我认为问题在于你假设编译器总是以“最佳”方式进行优化,缺陷在于你的假设不是编译器也不是优化器。gcc是开源的……如果你不喜欢它,就改变它……如果((r=x-y)>x)
图案更好。事实上,这是下面的答案之一。既然两者都是无符号的,
(a
,有什么问题吗?这是因为我想从a中减去b,如果我这样做(aa”的这种特殊情况,但不是我用a_a;})然后我只使用if(subchk(a,b)){underflow;}。我使用宏是因为我希望它与类型无关。出于某种原因,GCC有时在我的测试中现在使用“mov rsi,local_var”,然后使用“sub rsi,B”(我想要的sub),并将值放回本地堆栈“mov local_var,rsi”,然后进行跳转。当然,现在情况不同了。
extern void underflow(void) __attribute__((noreturn));
unsigned foo(unsigned a, unsigned b)
{
    unsigned r = a - b;
    if (r > a)
    {
        underflow();
    }
    return r;
}
foo:
    movl    %edi, %eax
    subl    %esi, %eax
    jb      .L6
    rep
    ret
.L6:
    pushq   %rax
    call    underflow