检查是否设置了进位标志

时间:2010-06-29 10:24:19

标签: c gcc x86 intel

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

4 个答案:

答案 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进行编译。