Какова цель "И AL,0xFF"?

Я читаю через разобранную программу 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, значения в AL, CL, а также DL перемещаются в [ESI + n],

1 ответ

Решение

Как предположил @fuz, это просто ошибка оптимизатора, который не распознает foo & 0xff как неиспользуемый в контексте, в котором он наиболее вероятно использовался в исходной функции.

Я скомпилировал следующий фрагмент кода с Borland C++ Builder 6 после того, как установил для параметров проекта "Release":

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

Это похоже на операции, выполняемые при разборке, которую вы предоставили достаточно близко. У нас есть 32-битное значение, которое мы хотим сдвинуть на указанное количество битов, а затем превратить его в байтовое значение, по существу возвращая биты 16-23 исходного значения в виде одного байта. Входной параметр имеет тип int для того, чтобы генерировать sar инструкция вместо shr: скорее всего, int также использовался в оригинальном коде.

После компиляции и дизассемблирования получившегося.obj с помощью objconv (поскольку я не мог понять, как включить списки сборок из IDE C++ Builder), я получил следующее:

@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 все еще там. Непосредственный 32-разрядный в разборке может быть проигнорирован, поскольку кодирование инструкции ясно показывает, что непосредственный поток в фактическом кодовом потоке является 8-разрядным: в любом случае других допустимых опций с 8-разрядным регистром нет.

Microsoft Visual Studio C++ 6, кажется, виновна в том же самом, но работает со всем 32-битным регистром (таким образом генерируя на 3 байта больше из-за непосредственного 32-битного), очищая старшие биты - что не нужно, видя, как возвращаемое значение функции было явно объявлено как 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

Между тем, самая старая версия gcc, доступная на godbolt, правильно компилирует это в то, что по сути является просто сдвигом, за исключением естественных различий между списками из-за соглашений о вызовах.

Другие вопросы по тегам