检查是否设置了进位标志

使用内联汇编程序[gcc,intel,c],如何在操作后检查进位标志是否设置?

使用条件跳转jc (如果进位则跳转)或jnc (如果不进位则跳转)。

或者你可以存储进位标志,

 ;; Intel syntax mov eax, 0 adc eax, 0 ; add with carry 

sbb %eax,%eax如果设置了进位标志sbb %eax,%eax将在eax中存储-1,如果清除则为0。 没有必要预先将eax清除为0; 从中减去eax会为你做这件事。 这种技术非常强大,因为您可以将结果用作位掩码来修改计算结果,而不是使用条件跳转。

您应该知道,只有在通过内联asm块执行的算术运算设置进位标志时才有效。 您无法测试在C代码中执行的计算的进位,因为编译器可以通过各种方式优化/重新排序会破坏进位标志的内容。

然而,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 instriuction的循环快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%。

第一个函数执行无符号加法,然后使用进位标志(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; } 

下一个函数用于签名的int。 同样使用挥发性物质。 请注意,有符号整数数学通过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; } 

在大图中,您可以使用以下function。 在同样的大图中,许多人可能会拒绝额外的工作和审美非美,直到溢出/包裹/下溢为止

 int r, a, b; ... if(!add_i32(a, b, &r)) abort(); // Integer overflow!!! ... 

内联GCC组件可在GCC 3.1及更高版本中使用。 请参阅使用C表达式操作数的汇编程序指令 ,或搜索“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进行编译。