【问题标题】:check if carry flag is set检查是否设置了进位标志
【发布时间】:2011-03-09 13:27:25
【问题描述】:

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

【问题讨论】:

  • 你想在一个 asm 块中测试这个,或者你想将进位标志的状态传递回你的 asm 内联的 C 代码中的某个东西?
  • 在一个 asm 块中测试就足够了。传递出去应该没有那么难。

标签: c gcc x86 carryflag eflags


【解决方案1】:

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

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

【讨论】:

    【解决方案2】:

    有条件跳转jc(进位跳转)或jnc(没有进位跳转)。

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

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

    【讨论】:

      【解决方案3】:

      但是,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% 左右。

      【讨论】:

      • 你有快速调用他们的引文吗?过去几代我都没有跟上最新的 cpu,但很长一段时间以来,这些都被认为是缓慢的遗留操作码。
      • @R.. 是的,SETcc 是旧指令,但比 ADC 或使用 JC 或 JNC 等标准跳转快得多。
      • 以上测试毫无意义。 (1),甚至没有使用结果,因此任何延迟都将被掩盖。并且 (2),非 SETcc 版本使用 mov/adc 而不是 sbb,它的字节数大 6 倍,操作码数加倍。如果您想对此进行基准测试,您应该使用实际使用结果进行计算的代码尝试这两个版本。
      • @R.. 添加了第二个测试!带有 SETcc 指令的 Rutine 部分仍然快 20% 左右。
      • 您仍在使用 mov/adc 而不是 sbb。用 sbb 试试,然后从“累加器”(CL)中减去结果(这将是 0 或 -1,因此在减去它时会加上 0 或 1)。还要确保您使用的是最短形式的指令 - 我不记得临时的但我似乎记得 sbb 可能更短,具体取决于您使用的寄存器或寄存器大小。由于缓存问题,更紧密的循环可能会运行得更快。
      【解决方案4】:

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

      【讨论】:

      • 您可以使用 goto 扩展来跳转到 C 标签,例如asm volatile goto("ja %l[clabel]" : : : "memory" : clabel );,其中 clabel 是 C 标签
      • 阅读下面@R.. 的答案似乎使您的功能无效。 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.你确定吗?
      • @DrBeco - 是的,对于 GCC,这取决于。 GCC 将确保块中指令的“连续性”,但它可能会插入/交错自己的指令。如果 GCC 指令不修改 CC,那么一切都会好起来的。微软的内联汇编器不受 GCC 的限制。
      • 用 volatile 写这么多代码是没有意义的,编译器无法优化任何东西。更好地编写纯 C 代码,例如 r = a + b;进位 = (r
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-12-29
      • 2018-03-25
      • 1970-01-01
      • 2010-11-23
      • 2019-08-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多