21

x86 アセンブリ命令がありますADC。これは「キャリーで追加」を意味することがわかりました。これはどういう意味ですか/何をしますか? この命令の動作を C++ でどのように実装しますか?

情報:
Windows でコンパイルされています。32 ビットの Windows インストールを使用しています。私のプロセッサは Intel の Core 2 Duo です。

4

6 に答える 6

31

ADCはADDと同じですが、プロセッサのキャリーフラグが設定されている場合はさらに1が追加されます。

于 2010-11-11T11:26:42.967 に答える
10

ここから(壊れた)またはここから

ただし、Intelプロセッサにはadcと呼ばれる特別な命令があります。このコマンドは、addコマンドと同様に動作します。唯一の余分なことは、値キャリーフラグも追加することです。したがって、これは大きな整数を追加するのに非常に便利です。16ビットレジスタを備えた32ビット整数を追加したいとします。どうすればそれができますか?さて、最初の整数がレジスタペアDX:AXに保持され、2番目の整数がBX:CXに保持されているとしましょう。こうやって:

add  ax, cx
adc  dx, bx

ああ、最初に、下位16ビットがadd ax、cxによって追加されます。次に、addの代わりにadcを使用して上位16ビットが追加されます。これは、オーバーフローが発生した場合、キャリービットが上位16ビットに自動的に追加されるためです。したがって、面倒なチェックは必要ありません。この方法は64ビットなどに拡張できます...注:32ビット整数の加算が上位16ビットでもオーバーフローすると、結果は正しくなく、キャリーフラグが設定されます(例:50億の加算)。 50億に。

これ以降はすべて、実装で定義された動作のゾーンにかなり分類されることを忘れないでください。

これは、VS 2010(32ビット、WinXp)で機能する小さなサンプルです。

警告:$ 7.4 / 1-「asm宣言は条件付きでサポートされています。その意味は実装で定義されています。[注:通常、情報を実装からアセンブラに渡すために使用されます。—endnote]」

int main(){
   bool carry = false;
   int x = 0xffffffff + 0xffffffff;
   __asm {
      jc setcarry
setcarry:
      mov carry, 1
   }
}
于 2010-11-11T11:31:11.963 に答える
9

ADCの動作は、CとC++の両方でシミュレートできます。次の例では、2つの数値を追加します(1つの符号なしに収まるには大きすぎるため、符号なしの配列として格納されます)。

unsigned first[10];
unsigned second[10];
unsigned result[11];

....   /* first and second get defined */

unsigned carry = 0;
for (i = 0; i < 10; i++) {
    result[i] = first[i] + second[i] + carry;
    carry = (first[i] > result[i]);
}
result[10] = carry;

お役に立てれば。

于 2010-11-11T12:01:40.550 に答える
8

C++ 言語にはキャリー フラグの概念がないため、ADC命令の周りに組み込み関数ラッパーを作成するのは面倒です。ただし、インテルはとにかくそれを行いました unsigned char _addcarry_u32 (unsigned char c_in, unsigned a, unsigned b, unsigned * out);。最後に確認したところ、gcc はこれをうまく処理できませんでした (キャリーの結果を CF に残すのではなく、整数レジスタに保存します) が、Intel 独自のコンパイラがうまく機能することを願っています。

アセンブリのドキュメントについては、 タグ wikiも参照してください。


コンパイラは、1 つのレジスタよりも広い整数を追加するときに ADC を使用します。たとえばint64_t、32 ビット コードまたは__int128_t64 ビット コードでの追加です。

#include <stdint.h>
#ifdef __x86_64__
__int128_t add128(__int128_t a, __int128_t b) { return a+b; }
#endif
    # clang 3.8 -O3  for x86-64, SystemV ABI.
    # __int128_t args passed in 2 regs each, and returned in rdx:rax
    add     rdi, rdx
    adc     rsi, rcx
    mov     rax, rdi
    mov     rdx, rsi
    ret

Godbolt コンパイラ Explorerからの asm 出力。clang-fverbose-asmはそれほど冗長ではありませんが、gcc 5.3 / 6.1 は 2 つmovの命令を浪費するため、読みにくくなっています。

イディオム/を使用adcするキャリーアウトを使用して、コンパイラを手に持って を発行したり、実行したりすることができる場合があります。しかし、これを拡張して、代わりにan からキャリーアウトを取得することは、現在のコンパイラでは不可能です。安全に実行した場合、コンパイラは複数のチェックを最適化してそれぞれを実行することができません。adduint64_t sum = a+b;carry = sum < a;adcaddc+d+carry_in+c+d+carry


クラン_ExtInt

add/adc/.../adc のチェーンを取得する方法が 1 つあります_ExtInt(width)。16,777,215 ビットまでの任意のサイズの固定ビット幅型を提供する Clang の新機能です (ブログ投稿)。2020 年 4 月 21 日に clang の開発バージョンに追加されたため、リリースされたバージョンにはまだ含まれていません。

これは、ある時点で ISO C および/または C++ に表示されることを願っています。N2472提案は明らかに「ISO WG14 C 言語委員会によって積極的に検討されている」ようです。

typedef _ExtInt(256) wide_int;

wide_int add ( wide_int a, wide_int b) {
    return a+b;
}

-O2x86-64 用のclang トランク( Godbolt )で次のようにコンパイルします。

add(int _ExtInt<256>, int _ExtInt<256>):
        add     rsi, r9
        adc     rdx, qword ptr [rsp + 8]
        adc     rcx, qword ptr [rsp + 16]
        mov     rax, rdi                        # return the retval pointer
        adc     r8, qword ptr [rsp + 24]        # chain of ADD / 3x ADC!

        mov     qword ptr [rdi + 8], rdx        # store results to mem
        mov     qword ptr [rdi], rsi
        mov     qword ptr [rdi + 16], rcx
        mov     qword ptr [rdi + 24], r8
        ret

明らか_ExtIntに、呼び出し規約がレジスターを使い果たすまで、整数レジスターで値によって渡されます。(少なくともこの初期のバージョンでは、おそらく x86-64 SysV は、16 バイトより大きい構造体のように、2 つまたは 3 つのレジスタより広い場合、それを「メモリ」として分類する必要があります。便利です。他の引数を最初に置いて、それらが置き換えられないようにしてください。)

最初の _ExtInt 引数は R8:RCX:RDX:RSI にあり、2 番目の下位 qword は R9 にあり、残りはメモリにあります。

戻り値オブジェクトへのポインターは、R​​DI の非表示の最初の引数として渡されます。x86-64 System V は最大 2 つの整数レジスタ (RDX:RAX) を返すだけであり、これはそれを変更しません。

于 2016-06-10T01:15:19.073 に答える
4

これにはバグがあります。この入力を試してください:

unsigned first[10] =  {0x00000001};
unsigned second[10] = {0xffffffff, 0xffffffff};

結果は {0, 0, 1, ...} のはずですが、結果は {0, 0, 0, ...} です

この行を変更します:

carry = (first[i] > result[i]);

これに:

if (carry)
    carry = (first[i] >= result[i]);
else
    carry = (first[i] > result[i]);

それを修正します。

于 2011-01-20T03:42:02.303 に答える