15

インラインアセンブラ[gcc、intel、c]を使用して、操作後にキャリーフラグが設定されているかどうかを確認するにはどうすればよいですか?

4

5 に答える 5

16

sbb %eax,%eaxキャリー フラグが設定されている場合は eax に -1 が格納され、クリアされている場合は 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命令では、2つのフラグを同時にテストできます。

編集: 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に格納される2番目のテストを追加しました。

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) を使用してオーバーフローをテストします。volatile はそのままにしておく必要があります。そうしないと、オプティマイザーが命令を再配置するため、結果が正しくないことがほぼ保証されます。オプティマイザーが を a に変更するのを見てきましjncjae(これも 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;
}

次の関数は、signed int 用です。volatile の同じ使用法が適用されます。符号付き整数演算は、 を介して OF フラグにジャンプすることに注意してくださいjnojnbオプティマイザーがこれを(これも 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;
}

全体像では、次のように関数を使用できます。同じ全体像では、オーバーフロー/ラップ/アンダーフローによって pwn されるまで、多くの人々はおそらく余分な作業と美的でない美しさを拒否するでしょう。

int r, a, b;
...

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

...

インライン GCC アセンブリは、GCC 3.1 以降で使用できます。C Expression Operands を使用した Assembler Instructions を参照するか、「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 を使用してコンパイルする必要があります。

于 2011-06-19T00:46:30.520 に答える