【发布时间】:2011-03-09 13:27:25
【问题描述】:
使用内联汇编器[gcc, intel, c],如何检查操作后是否设置了进位标志?
【问题讨论】:
-
你想在一个 asm 块中测试这个,或者你想将进位标志的状态传递回你的 asm 内联的 C 代码中的某个东西?
-
在一个 asm 块中测试就足够了。传递出去应该没有那么难。
标签: c gcc x86 carryflag eflags
使用内联汇编器[gcc, intel, c],如何检查操作后是否设置了进位标志?
【问题讨论】:
标签: c gcc x86 carryflag eflags
sbb %eax,%eax 如果设置了进位标志,则将 -1 存储在 eax 中,如果清除则存储 0。无需预先清除 eax 为 0;从自身中减去 eax 可以为您做到这一点。这种技术非常强大,因为您可以使用结果作为位掩码来修改计算结果,而不是使用条件跳转。
您应该知道,只有通过在内联 asm 块中执行的算术设置进位标志才有效。您无法测试在 C 代码中执行的计算的进位,因为编译器可以通过各种方式优化/重新排序会破坏进位标志的内容。
【讨论】:
有条件跳转jc(进位跳转)或jnc(没有进位跳转)。
或者你可以存储进位标志,
;; Intel syntax
mov eax, 0
adc eax, 0 ; add with carry
【讨论】:
但是,x86 汇编器有专用的 fast 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 中进行的简单测试,以消除对术语 fast
的疑问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 倍以上。
编辑:添加了第二个测试,该测试结果存储在寄存器 AL 中,在寄存器 CL 中可计算,以更现实的情况。
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% 左右。
【讨论】:
第一个函数执行无符号加法,然后使用进位标志 (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;
}
下一个函数用于有符号整数。同样使用 volatile 也适用。请注意,有符号整数数学通过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 程序集在 GCC 3.1 及更高版本中可用。请参阅Assembler Instructions with C Expression Operands,或搜索“GCC 扩展程序集”。
最后,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 进行编译。
【讨论】:
You should be aware that it is only valid to test the carry flag if it was set by arithmetic performed INSIDE the inline asm block.你确定吗?