5

Linux (x86) で GCC インライン アセンブリを学習しようとしています。最初の実験は、乗算の整数オーバーフロー検出を実装することでした。簡単に思えますが、理解できない副作用があります。

したがって、ここでは 2 つの符号なし 8 ビット整数を乗算し、結果がオーバーフローするかどうかを確認します。基本的に、最初のオペランドを AL レジスタにロードし、もう 1 つのオペランドを BL レジスタにロードしてから、mul命令を使用します。結果は 16 ビット値として AX レジスタに格納されます。bそのため、オーバーフローしない限り、AX レジスタの値を C 変数にコピーします。オーバーフローした場合はc1 に設定します。

 uint8_t a = 10;
 uint8_t b = 25;
 uint8_t c = 0; // carry flag

 __asm__
 (
  "clc;"                  // Clear carry flag
  "movb %3, %%al;"        // Load b into %al
  "movb %2, %%bl;"        // Load a into %bl 
  "mul %%bl;"             // Multiply a * b (result is stored in %ax)
  "movw %%ax, %0;"        // Load result into b
  "jnc out;"              // Jump to 'out' if the carry flag is not set
  "movb $1, %1;"          // Set 'c' to 1 to indicate an overflow
  "out:"
  :"=m"(b), "=m"(c)       // Output list
  :"ir"(a), "m"(b)        // Input list
  :"%al", "%bl"           // Clobbered registers (not sure about this)
 );

これはうまくいくようです。printf'b' の値が 250 である場合、これは正しいです。また、「b」の開始値を 26 に変更すると、乗算後cに 1 に設定され、もちろん (10 * 26 > ~uint8_t(0)) によるオーバーフローを示します。私が見ている問題はa、乗算後に C 変数が 0 (またはオーバーフローで 1) に設定aされていることです。出力変数のリストにも載っていないのに、アセンブリ ルーチンが の値に影響するのはなぜaですか?

また、破壊されたレジスタのリストについてもよくわかりません。このリストは、GCC がそれらを誤って使用しようとしないように、アセンブリ ルーチン中に使用されたレジスタについて GCC に通知することになっています。AL レジスタと BL レジスタを使用したことを GCC に通知する必要があると思いますが、AX レジスタはどうでしょうか。2 つの 8 ビット整数の積を格納するために暗黙的に使用されているため、破壊されたレジスタのリストに含める必要がありますか?

4

2 に答える 2

7

私が見ている問題はa、乗算後に C 変数が 0 (またはオーバーフローで 1) に設定aされていることです。出力変数のリストにも載っていないのに、アセンブリ ルーチンが の値に影響するのはなぜaですか?

mul %%blAL (8 ビット) を BL (8 ビット) で乗算し、結果を AX (16 ビット) に入れます。

AL と AX は別個のレジスタではないことに注意してください。AL は AX の下位 8 ビットです。

movw %%ax, %0は AX (16 ビット) をb...のアドレスに格納しますuint8_t。したがって、この命令はメモリ内の次のバイトを結果の上位 8 ビットで上書きします。この場合、そのバイトはたまたま の値が格納される場所になります(オーバーフローしていない場合は 0 で上書きされ、オーバーフローしている場合は 1 で上書きaされる理由が説明されています)。a

movb %%al, %0結果の下位8ビットのバイトストアのみを行うには、それを に置き換える必要があります。

AL レジスタと BL レジスタを使用したことを GCC に通知する必要があると思いますが、AX レジスタはどうでしょうか。2 つの 8 ビット整数の積を格納するために暗黙的に使用されているため、破壊されたレジスタのリストに含める必要がありますか?

はい-値を変更するレジスタについてGCCに通知する必要があります(また、別の回答でnategooseが指摘したように、フラグも変更していることをおそらく通知する必要があります)。したがって、ここの clobber リストは次のようになります"%ax", "%bl", "cc"(AX には AL が含まれているため、明示的に AL について言及する必要はありません)。

于 2010-10-07T22:05:27.677 に答える
2

オプションを指定してコードをコンパイル-Sし、*.s ファイルを確認する必要があります。あなたのアセンブリはすべてセミコロンで区切られた同じ行にあります.gnuアセンブラではセミコロンがコメントの始まりだと思います. すべての組み立て手順の末尾に「\n」(または「\n\t」) を追加する必要があります。

clobber リストに「cc」を追加することもできます。

さらに、GCC には、入力がアセンブリへの入力と出力の両方であることを指定する方法があります。

また、入力を強制的にメモリに配置するのではなく、GCC に入力の場所を決定させるのがおそらく最善です。GCC のインライン アセンブリには x86 に関する制約があり、それは問題にならない状況で使用できる「レジスタ内またはメモリ内」を意味することを覚えているようですが、おそらく「mov」命令を多く持つべきではありませんGCC の最大の仕事の 1 つは、実際の計算命令の間に挿入する移動命令の最適なセットを見つけ出すことだからです。たとえば、コードで GCC が行う最善の方法は、最初に使用しているレジスタに定数 10 と 25 を格納することです。

于 2010-10-07T17:46:31.003 に答える