使用内联汇编程序[gcc,intel,c],如何在操作后检查进位标志是否设置?
答案 0 :(得分:14)
sbb %eax,%eax
将在-1中存储-1,如果清除则为0。没有必要预先将eax清除为0;从中减去eax会为你做这件事。这种技术非常强大,因为您可以将结果用作位掩码来修改计算结果,而不是使用条件跳转。
您应该知道,只有通过内联asm块内部执行的算术设置才能测试进位标志。您无法测试在C代码中执行的计算的进位,因为编译器可以通过各种方式优化/重新排序会破坏进位标记的内容。
答案 1 :(得分:10)
使用条件跳转jc
(如果进位则跳转)或jnc
(如果不进位则跳转)。
或者你可以存储进位标志,
;; Intel syntax
mov eax, 0
adc eax, 0 ; add with carry
答案 2 :(得分:5)
然而,x86汇编程序专用快速 ALU标志测试指令名为SETcc,其中cc是所需的ALU标志。所以你可以写:
setc AL //will set AL register to 1 or clear to 0 depend on carry flag
or
setc byte ptr [edx] //will set memory byte on location edx depend on carry flag
or even
setc byte ptr [CarryFlagTestByte] //will set memory variable on location CarryFlagTestByte depend on carry flag
使用 SETcc 指令,您可以测试进位,零,符号,溢出或奇偶校验等标志,一些 SETcc 指令允许一次测试两个标志。
修改强> 添加了Delphi中的简单测试,以消除对术语快速
的疑问procedure TfrmTest.ButtonTestClick(Sender: TObject);
function GetCPUTimeStamp: int64;
asm
rdtsc
end;
var
ii, i: int64;
begin
i := GetCPUTimeStamp;
asm
mov ecx, 1000000
@repeat:
mov al, 0
adc al, 0
mov al, 0
adc al, 0
mov al, 0
adc al, 0
mov al, 0
adc al, 0
loop @repeat
end;
i := GetCPUTimeStamp - i;
ii := GetCPUTimeStamp;
asm
mov ecx, 1000000
@repeat:
setc al
setc al
setc al
setc al
loop @repeat
end;
ii := GetCPUTimeStamp - ii;
caption := IntToStr(i) + ' ' + IntToStr(ii));
end;
使用指令 setc 的循环(1M次迭代)比使用 adc 感兴趣的循环快5倍以上。
编辑:在寄存器CL中添加了第二个测试,其中存储在寄存器AL中的测试结果是更实际的情况。
procedure TfrmTestOtlContainers.Button1Click(Sender: TObject);
function GetCPUTimeStamp: int64;
asm
rdtsc
end;
var
ii, i: int64;
begin
i := GetCPUTimeStamp;
asm
xor ecx, ecx
mov edx, $AAAAAAAA
shl edx, 1
mov al, 0
adc al, 0
add cl, al
shl edx, 1
mov al, 0
adc al, 0
add cl, al
shl edx, 1
mov al, 0
adc al, 0
add cl, al
shl edx, 1
mov al, 0
adc al, 0
add cl, al
shl edx, 1
mov al, 0
adc al, 0
add cl, al
shl edx, 1
mov al, 0
adc al, 0
add cl, al
shl edx, 1
mov al, 0
adc al, 0
add cl, al
shl edx, 1
mov al, 0
adc al, 0
add cl, al
end;
i := GetCPUTimeStamp - i;
ii := GetCPUTimeStamp;
asm
xor ecx, ecx
mov edx, $AAAAAAAA
shl edx, 1
setc al
add cl, al
shl edx, 1
setc al
add cl, al
shl edx, 1
setc al
add cl, al
shl edx, 1
setc al
add cl, al
shl edx, 1
setc al
add cl, al
shl edx, 1
setc al
add cl, al
shl edx, 1
setc al
add cl, al
shl edx, 1
setc al
add cl, al
end;
ii := GetCPUTimeStamp - ii;
caption := IntToStr(i) + ' ' + IntToStr(ii);
end;
使用SETcc指令的Rutine部分仍然快20%左右。
答案 3 :(得分:2)
第一个函数执行无符号加法,然后使用进位标志(CF)测试溢出。必须保持挥发性。否则优化器将重新排列指令,这几乎可以确保不正确的结果。我已经看到优化器将jnc
更改为jae
(也基于CF)。
/* Performs r = a + b, returns 1 if the result is safe (no overflow), 0 otherwise */
int add_u32(uint32_t a, uint32_t b, uint32_t* r)
{
volatile int no_carry = 1;
volatile uint32_t result = a + b;
asm volatile
(
"jnc 1f ;"
"movl $0, %[xc] ;"
"1: ;"
: [xc] "=m" (no_carry)
);
if(r)
*r = result;
return no_carry;
}
下一个功能是签名的整数。同样使用挥发性物质。请注意,有符号整数数学通过jno
跳过OF标志。我已经看到优化器将其更改为jnb
(也基于OF)。
/* Performs r = a + b, returns 1 if the result is safe (no overflow), 0 otherwise */
int add_i32(int32_t a, int32_t b, int32_t* r)
{
volatile int no_overflow = 1;
volatile int32_t result = a + b;
asm volatile
(
"jno 1f ;"
"movl $0, %[xo] ;"
"1: ;"
: [xo] "=m" (no_overflow)
);
if(r)
*r = result;
return no_overflow;
}
在大图中,您可以使用以下功能。在同样的大图中,许多人可能会拒绝额外的工作和审美非美,直到溢出/包裹/下溢为止
int r, a, b;
...
if(!add_i32(a, b, &r))
abort(); // Integer overflow!!!
...
GCC 3.1及以上版本中提供了内联GCC组件。请参阅Assembler Instructions with C Expression Operands,或搜索“GCC Extended Assembly”。
最后,Visual Studio中的相同内容如下(代码生成没有太大区别),但语法更容易,因为MASM允许您跳转到C标签:
/* Performs r = a + b, returns 1 if the result is safe (no overflow), 0 otherwise */
int add_i32(__int32 a, __int32 b, __int32* r)
{
volatile int no_overflow = 1;
volatile __int32 result = a + b;
__asm
{
jno NO_OVERFLOW;
mov no_overflow, 0;
NO_OVERFLOW:
}
if(r)
*r = result;
return no_overflow;
}
不好的是,上面的MASM代码仅适用于x86程序集。对于x64程序集,没有内联,因此您必须在程序集中(在单独的文件中)对其进行编码,并使用MASM64进行编译。