15

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

4

5 回答 5

16

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

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

于 2010-06-29T13:16:22.420 回答
11

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

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

;; Intel syntax
mov eax, 0
adc eax, 0 ; add with carry
于 2010-06-29T10:35:15.837 回答
6

然而,x86 汇编器有一个名为 SETcc 的专用快速ALU 标志测试指令,其中 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 倍以上。

编辑:添加了第二个测试,该测试结果存储在寄存器 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%。

于 2010-06-29T15:42:14.887 回答
2

第一个函数执行无符号加法,然后使用进位标志 (CF) 测试溢出。挥发物必须保留。否则优化器将重新排列指令,这几乎可以确保不正确的结果。我已经看到优化器将 更改jnc为 a 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. 我已经看到优化器将其更改为 a 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 及更高版本中可用。请参阅带有 C 表达式操作数的汇编器指令,或搜索“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 进行编译。

于 2011-06-19T00:46:30.520 回答
-1

如果它是正确的,这可能会给出一个想法或解决方案。我一直在努力测试环绕,直到我发现了内联汇编。我尝试使用各种边缘值进行测试,并且似乎工作正常。程序从 cmdln 获取输入并将其转换为整数并输出十六进制和二进制值。

gcc 版本 11.2.1

$> gcc -Wall -std=c99 -O2 -o uilt uilt.c

片段:

size_t i = 0;
int mul = 10;
uint128_t sum = 0;
int int_array[48] = {0};

// fill arr. with ea. str val in argv[1] str. converted to int vals.
while (i < strlen(argv[1])) {
  // chk they are digit chars, if not, skip iter
  if (isdigit(argv[1][i]) == 0) {
    i++;
    continue;
  }
  int_array[i] = (argv[1][i] - 48);
  sum = int_array[i] + (sum * mul);

  /* check carry flag */
  __asm__ goto("jc %l0"
               : /* no outputs  */
               : /* no inputs   */
               : /* no clobbers */
               : carry);

  /* no carry */
  goto its_good;

 carry:
  system("clear");
  printf("\n\n\tERROR!!!\
        \n\n\t!!!!!!! uilt has ABORTED !!!!!!\
        \n\tCmdln arg exceeds 2^127 bit limit\
        \n\twhen converted from string to 127\
        \n\tbit unsigned __int128.\n\n");
  exit(1);

 its_good:
  i++;
 }

一些输出:

[jim@nitroII uiltDev]$ ./uilt 1

Dec: 1

Hex: 0x0001

Bin: 0x0001

[jim@nitroII uiltDev]$ ./uilt 255

Dec: 255

Hex: 0x00ff

Bin: 0x0000 1111 1111

[jim@nitroII uiltDev]$ ./uilt 18446744073709551616

Dec: 18446744073709551616

Hex: 0x0001 0000 0000 0000 0000

Bin: 0x0001 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000

[jim@nitroII uiltDev]$ ./uilt 340282366920938463463374607431768211455

Dec: 340282366920938463463374607431768211455

Hex: 0x0000 ffff ffff ffff ffff ffff ffff ffff ffff

Bin: 0x0000 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111

Dec: 340282366920938463463374607431768211456

        ERROR!!!            

        !!!!!!! uilt has ABORTED !!!!!!            
        Cmdln arg exceeds 2^127 bit limit            
        when converted from string to 127            
        bit unsigned __int128.
于 2022-01-04T08:21:40.450 回答