Дизассемблирование

Этот код, правда, все же не быстрее предыдущего, не оптимизированного, и укладывается в те же три такта, но в других случаях выигрыш может оказаться вполне ощутимым.

Другие компиляторы так же используют LEA для быстрого умножения чисел. Вот, к примеру, Borland поступает так: _main proc near ; DATA XREF: DATA:00407044 olea edx, [eax+eax*2]; EDX = var_a*3 mov ecx, eax; Загружаем в ECX неинициализированную регистровую переменную var_a shl ecx, 2; ECX = var_a * 4 push ebp; Сохраняем EBP add ecx, 5; Добавляем к var_a * 4 значение 5; К сожалению Borland не использует LEA для сложения. lea edx, [eax+edx*4]; EDX = var_a + (var_a *3) *4 = var_a * 13; Здесь Borland и MS единодушны. mov ebp, esp; Открываем кадр стека в середине функции.; Выше находтся "потерянная" команда push EBP push edx; Передаем printf произведение var_a * 13 shl eax, 4; Умножаем ((var_a *4) + 5) на 16; Здесь проискодит глюк компилятора, посчитавшего что: раз переменная var_a; неинициализирована, то ее можно и не загружать… push ecxpush eaxpush offset aXXX ; "%x %x %x\n"call printfadd esp, 10hxor eax, eaxpop ebpretn _main endp

Хотя "визуально" Borland генерирует не очень “красивый” код, его выполнение укладывается в те же три такта процессора. Другое дело WATCOM, показывающий удручающе отсталый результат на фоне двух предыдущих компиляторов: main proc nearpush ebx; Сохраняем EBX в стеке mov eax, ebx; Загружаем в EAX значение неинициализированной регистровой переменной var_a shl eax, 2; EAX = var_a * 4 sub eax, ebx; EAX = var_a * 4 - var_a = var_a * 3; Здесь WATCOM! Сначала умножает "с запасом", а потом лишнее отнимает! shl eax, 2; EAX = var_a * 3 * 4 = var_a * 12 add eax, ebx; EAX = var_a * 12 + var_a = var_a * 13; Четыре инструкции, в то время как; Microsoft Visual C++ вполне обходится и двумя! push eax; Передаем printf значение var_a * 13 mov eax, ebx; Загружаем в EAX значение неинициализированной регистровой переменной var_a shl eax, 2; EAX = var_a * 4 add eax, 5; EAX = var_a * 4 + 5 ; Пользоваться LEA WATCOM то же не умеет! push eax; Передаем printf значение var_a * 4 + 5 shl ebx, 4; EBX = var_a * 16 push ebx; Передаем printf значение var_a * 16 push offset aXXX ; "%x %x %x\n"call printf_add esp, 10h; printf("%x %x %x\n",var_a * 16, var_a * 4 + 5, var_a*13) pop ebx retn main_ endp

В результате, код, сгенерированный компилятором WATCOM требует шести тактов, т.е. вдвое больше, чем у конкурентов.

1.6 Комплексные операторы

Язык Си\Си++ выгодно отличается от большинства своих конкурентов поддержкой комплексных операторов: x= (где x - любой элементарный оператор), ++ и - -. Комплексные операторы семейства "a x= b" транслируются в "a = a x b" и они идентифицируются так же, как и элементарные операторы. Операторы "++" и "--": в префиксной форме они выражаются в тривиальные конструкции "a = a +1" и "a = a - 1" и не представляющие никакого интереса.

2. Идентификация SWITCH - CASE - BREAK

Для улучшения читабельности программ в язык Си был введен оператор множественного выбора – switch. В Паскале с той же самой задачей справляется оператор CASE.

Легко показать, что switch эквивалентен конструкции "IF (a == x1) THEN оператор1 ELSE IF (a == x2) THEN оператор2 IF (a == x2) THEN оператор2 IF (a == x2) THEN оператор2 ELSE …. оператор по умолчанию". Если изобразить это ветвление в виде логического дерева, то образуется характерная "косичка", прозванная так за сходство с завитой в косу прядью волос – см. рис. 1

Казалось бы, идентифицировать switch никакого труда не составит, – даже не строя дерева, невозможно не обратить внимания на длинную цепочку гнезд, проверяющих истинность условия равенства некоторой переменной с серией непосредственных значений (сравнения переменной с другой переменной switch не допускает).

Трансляция оператора switch в общем случае

Рисунок 1 Трансляция оператора switch в общем случае

Однако в реальной жизни все происходит совсем не так. Компиляторы (даже не оптимизирующие) транслируют switch в настоящий "мясной рулет", содержащий всевозможные операции отношений. Вот что из этого выйдет, откомпилировав приведенный выше пример компилятором Microsoft Visual C++:

main proc near ; CODE XREF: start+AF p var_tmp = dword ptr -8var_a = dword ptr –4 push ebpmov ebp, esp; Открываем кадр стека sub esp, 8; Резервируем место для локальных переменных mov eax, [ebp+var_a]; Загружаем в EAX значение переменной var_a mov [ebp+var_tmp], eax; Обратите внимание – switch создает собственную временную переменную!; Даже если значение сравниваемой переменной в каком-то ответвлении CASE; будет изменено, это не повлияет на результат выборов!; В дальнейшем во избежании путаницы, следует условно называть; переменную var_tmp переменной var_a cmp [ebp+var_tmp], 2; Сравниваем значение переменной var_a с двойкой; В исходном коде CASE начинался с нуля, а заканчивался 0x666 jg short loc_401026; Переход, если var_a > 2; Обратите на этот момент особое внимание – ведь в исходном тексте такой; операции отношения не было!; Причем, этот переход не ведет к вызову функции printf, т.е. этот фрагмент; кода получен не прямой трансляцией некой ветки case, а как-то иначе! cmp [ebp+var_tmp], 2; Сравниваем значение var_a с двойкой; Очевидный "прокол" компилятора (обратите внимание никакие флаги не менялись!) jz short loc_40104F; Переход к вызову printf("a == 2"), если var_a == 2; Этот код явно получен трансляцией ветки CASE 2: printf("a == 2") cmp [ebp+var_tmp], 0; Сравниваем var_a с нулем jz short loc_401031; Переход к вызову printf("a == 0"), если var_a == 0; Этот код получен трансляцией ветки CASE 0: printf("a == 0") cmp [ebp+var_tmp], 1; Сравниваем var_a с единицей jz short loc_401040; Переход к вызову printf("a == 1"), если var_a == 1; Этот код получен трансляцией ветки CASE 1: printf("a == 1") jmp short loc_40106D; Переход к вызову printf("Default"); Этот код получен трансляцией ветки Default: printf("a == 0") loc_401026: ; CODE XREF: main+10 j; Эта ветка получает управление, если var_a > 2cmp [ebp+var_tmp], 666h; Сравниваем var_a со значением 0x666 jz short loc_40105E; Переход к вызову printf("a == 666h"), если var_a == 0x666; Этот код получен трансляцией ветки CASE 0x666: printf("a == 666h") jmp short loc_40106D; Переход к вызову printf("Default"); Этот код получен трансляцией ветки Default: printf("a == 0") loc_401031: ; CODE XREF: main+1C j; // printf("A == 0")push offset aA0 ; "A == 0"call _printfadd esp, 4jmp short loc_40107A; ^^^^^^^^^^^^^^^^^^^^^^ - а вот это оператор break, выносящий управление; за пределы switch – если бы его не было, то начали бы выполняться все; остальные ветки CASE, не зависимо от того, к какому значению var_a они; принадлежат! loc_401040: ; CODE XREF: main+22 j; // printf("A == 1")push offset aA1 ; "A == 1"call _printfadd esp, 4jmp short loc_40107A; ^ break loc_40104F: ; CODE XREF: main+16 j; // printf("A == 2")push offset aA2 ; "A == 2"call _printfadd esp, 4jmp short loc_40107A; ^ break loc_40105E: ; CODE XREF: main+2D j; // printf("A == 666h")push offset aA666h ; "A == 666h"call _printfadd esp, 4jmp short loc_40107A; ^ break loc_40106D: ; CODE XREF: main+24 j main+2F j; // printf("Default")push offset aDefault ; "Default"call _printfadd esp, 4 loc_40107A: ; CODE XREF: main+3E j main+4D j .; // КОНЕЦ SWITCHmov esp, ebppop ebp; Закрываем кадр стекаretnmain endp


Страница: