3

逆アセンブルされた win32 C++ プログラムを読んでいると、かなりの数が表示されます。

AND AL,0xFF

これは完全に無意味ですか、それともコンパイラがこれらを生成するのはなぜですか?

より長い例を次に示します。

movsx   eax, byte ptr [ebx]
shl     eax, 18h
movsx   edx, byte ptr [ebx+1]
shl     edx, 10h
add     eax, edx
movsx   ecx, byte ptr [ebx+2]
shl     ecx, 8
add     eax, ecx
movsx   edx, byte ptr [ebx+3]
add     eax, edx
xor     edx, edx
call    sub_43B55C
mov     ecx, eax
mov     edx, eax
sar     ecx, 10h
and     al, 0FFh      # <----
sar     edx, 8
and     cl, 0FFh      # <----
mov     [esi], cl
and     dl, 0FFh      # <----
mov     [esi+1], dl
mov     [esi+2], al
add     ebx, 4
add     esi, 3
inc     ebp
cmp     ebp, 6
jl      short loc_43B5E4

これらの操作の後にフラグがチェックされていないため、それは目的ではありません。の後、、、およびANDの値が に移動されます。ALCLDL[ESI + n]

4

1 に答える 1

4

@fuz が示唆したように、これはオプティマイザーfoo & 0xffが元の関数で使用された可能性が最も高いコンテキストでノーオペレーションであると認識していないという単純な障害です。

プロジェクトのコンパイル設定を "Release" に設定した後、Borland C++ Builder 6 で次のコード スニペットをコンパイルしました。

unsigned char foobar(int foo) { return (foo >> 16) & 0xff; }

これは、提供した逆アセンブリで実行された操作と非常によく似ています。32 ビット値を指定したビット数だけシフトしてバイト値に変換し、基本的に元の値のビット 16 ~ 23 を 1 バイトとして返します。入力パラメーターは、 の代わりに命令intを生成するためのタイプです。おそらく元のコードでも an が使用されていました。sarshrint

結果の .obj をobjconvでコンパイルおよび逆アセンブルした後(C++ Builder の IDE 内からアセンブリ リストを有効にする方法がわからなかったため)、次のようになりました。

@foobar$qi PROC NEAR
;  COMDEF @foobar$qi
        push    ebp                                     ; 0000 _ 55
        mov     ebp, esp                                ; 0001 _ 8B. EC
        mov     eax, dword ptr [ebp+8H]                 ; 0003 _ 8B. 45, 08
        sar     eax, 16                                 ; 0006 _ C1. F8, 10
        and     al, 0FFFFFFFFH                          ; 0009 _ 24, FF
        pop     ebp                                     ; 000B _ 5D
        ret                                             ; 000C _ C3
@foobar$qi ENDP

ご覧のとおり、冗長性andはまだ残っています。命令のエンコーディングは、実際のコード ストリームの即値が 8 ビットであることを明確に示しているため、逆アセンブリの 32 ビットの即値は無視できます。とにかく、8 ビット レジスタを使用する有効なオプションは他にありません。

Microsoft Visual Studio C++ 6 も同じことを犯しているようですが、32 ビット レジスタ全体で動作し (したがって、32 ビットの即値のために 3 バイト多く生成されます)、上位ビットをクリアします。これは不要です。関数の戻り値は明示的に 8 ビットであると宣言されました:

?foobar@@YAEH@Z PROC NEAR                               ; foobar
; 1    : unsigned char foobar(int foo) { return (foo >> 16) & 0xff; }
  00000 55               push    ebp
  00001 8b ec            mov     ebp, esp
  00003 8b 45 08         mov     eax, DWORD PTR _foo$[ebp]
  00006 c1 f8 10         sar     eax, 16                        ; 00000010H
  00009 25 ff 00 00 00   and     eax, 255               ; 000000ffH
  0000e 5d               pop     ebp
  0000f c3               ret     0
?foobar@@YAEH@Z ENDP                                    ; foobar

一方、godbolt で利用可能な gcc の最も古いバージョンは、呼び出し規則によるリスト間の自然な違いを除いて、これを本質的に単なるシフトに正しくコンパイルします。

于 2017-07-27T23:07:34.620 に答える