Какова цель "И 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, правильно компилирует это в то, что по сути является просто сдвигом, за исключением естественных различий между списками из-за соглашений о вызовах.