我使用GCC 4.8.1编译C代码,我需要检测x86 / 64架构上的减法是否发生下溢。两者都是UNSIGNED。我知道在汇编中非常容易,但是我想知道我是否可以用C代码完成它并让GCC以某种方式优化它,因为我无法找到它。这是一个非常常用的函数(或低级,是术语吗?)所以我需要它才能高效,但是GCC似乎太愚蠢了,无法识别这个简单的操作?我尝试了很多方法在C中给它提示,但它总是使用两个寄存器而不是一个子和一个条件跳转。说实话,我很生气地看到这么多次编写的愚蠢代码(函数被称为 lot )。
我在C中的最佳方法似乎如下:
if((a-=b)+b < b) {
// underflow here
}
基本上,从a中减去b,如果结果下溢检测到它并进行一些条件处理(例如,它与某个值无关,则会带来错误等)。
GCC似乎太愚蠢了,不能将上面的内容简化为一个sub和一个条件跳转,并且相信我在C代码中尝试了很多方法,并尝试了很多命令行选项(-O3和-Os包括课程)。 GCC的作用是这样的(英特尔语法汇编):mov rax, rcx ; 'a' is in rcx
sub rcx, rdx ; 'b' is in rdx
cmp rax, rdx ; useless comparison since sub already sets flags
jc underflow
毋庸置疑,上述内容是愚蠢的,只要它需要的是:
sub rcx, rdx
jc underflow
这太烦人了,因为GCC确实理解sub修改标志的方式,因为如果我将它转换为&#34; int&#34;除了使用&#34; js&#34;它将生成上面的确切内容。这是带符号跳转而不是进位,如果无符号值差异足以设置高位,则无法工作。然而,它表明它知道影响那些标志的子指令。
现在,也许我应该放弃尝试让GCC正确地优化它并使用内联汇编进行,我没有遇到任何问题。不幸的是,这需要&#34; asm goto&#34;因为我需要一个有条件的JUMP,并且asm goto对输出的效率不高,因为它是不稳定的。
我尝试了一些东西,但我不知道它是否安全&#34;使用与否。 asm goto由于某种原因无法输出。我不想让它将所有寄存器刷新到内存中,这会杀死我执行此操作的整个点,这就是效率。但是如果我使用空的asm语句,输出设置为&#39; a&#39;它之前和之后的变量,是否有效并且安全吗?这是我的宏:
#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
它有点难看,但效果很好。在我的测试场景中,它只编译FINE而没有易失性开销(将寄存器刷新到内存中)而不会产生任何不好的东西,它似乎工作正常,但这只是一个有限的测试,我无法在我使用的任何地方测试它这个函数/宏,正如我所说的那样使用很多,所以我想知道某人是否知识渊博,上述结构是否存在不安全之处?
特别是&#39; a&#39;如果发生下溢则不需要,所以考虑到这一点,我的内联asm宏可能会发生任何副作用或不安全的事情吗?如果没有,我会毫无问题地使用它,直到他们优化编译器,所以我可以在我猜之后将其替换回来。
请不要把这变成关于过早优化的讨论,或者不要讨论问题的主题,我完全清楚这一点,谢谢。
答案 0 :(得分:3)
我可能会错过一些明显的东西,但为什么这不好呢?
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变得简单。
答案 1 :(得分:0)
以下汇编代码怎么样(你可以把它换成GCC格式):
sub rcx, rdx ; assuming operands are in rcx, rdx
setc al ; capture carry bit int AL (see Intel "setxx" instructions)
; return AL as boolean to compiler
然后调用/内联汇编代码,并对结果布尔值进行分支。
答案 2 :(得分:0)
您是否测试过这是否实际更快?现代x86-microarchitectures使用微代码,将单个汇编指令转换为更简单的微操作序列。其中一些还进行微操作融合,其中一系列汇编指令被转换为单个微操作。特别是像test %reg, %reg; jcc target
这样的序列被融合,可能是因为全局处理器标志是性能的祸根
如果cmp %reg, %reg; jcc target
是mOp融合的,gcc可能会使用它来获得更快的代码。根据我的经验,gcc 非常擅长调度和类似的低级优化。