Невозможно отучить людей изучать самые ненужные предметы.
Введение в CSS
Преимущества стилей
Добавления стилей
Типы носителей
Базовый синтаксис
Значения стилевых свойств
Селекторы тегов
Классы
CSS3
Надо знать обо всем понемножку, но все о немногом.
Идентификаторы
Контекстные селекторы
Соседние селекторы
Дочерние селекторы
Селекторы атрибутов
Универсальный селектор
Псевдоклассы
Псевдоэлементы
Кто умеет, тот делает. Кто не умеет, тот учит. Кто не умеет учить - становится деканом. (Т. Мартин)
Группирование
Наследование
Каскадирование
Валидация
Идентификаторы и классы
Написание эффективного кода
Вёрстка
Изображения
Текст
Цвет
Линии и рамки
Углы
Списки
Ссылки
Дизайны сайтов
Формы
Таблицы
CSS3
HTML5
Блог для вебмастеров
Новости мира Интернет
Сайтостроение
Ремонт и советы
Все новости
Справочник от А до Я
HTML, CSS, JavaScript
Афоризмы о учёбе
Статьи об афоризмах
Все Афоризмы
| Помогли мы вам |
Для улучшения читабельности программ в язык C был введен оператор множественного выбора — switch. В Delphi с той же самой задачей справляется оператор CASE, более гибкий, чем его C-аналог, но об их различиях мы поговорим позднее.
Легко показать, что switch эквивалентен такой конструкции:
IF (a == x1) THEN оператор1
ELSE IF (a == X2) THEN оператор2
IF (a == X2) THEN оператор2
IF (a == X2) THEN оператор2
ELSE ... оператор по умолчанию
Если изобразить это ветвление в виде логического дерева, то образуется характерная «косичка».
Казалось бы, идентифицировать switch никакого труда не составит — даже не строя дерева, невозможно не обратить внимание на длинную цепочку гнезд, проверяющих истинность условия равенства некоторой переменной с серией непосредственных значений (сравнения переменной с другой переменной оператор switch не допускает).
Однако в реальной жизни все происходит совсем не так. Компиляторы (даже неоптимизирующие) транслируют switch в настоящий «мясной рулет», доверху нашпигованный всевозможными операциями отношений. Давай откомпилируем следующий код компилятором Microsoft Visual C++ 2022:
#include <stdio.h>int main(){ int a = 0x666; switch (a) { case 0:printf("a == 0");break; case 1:printf("a == 1");break; case 2:printf("a == 2");break; case 0x666:printf("a == 666h");break; default:printf("Default"); }}Вывод приложения switch_casesТеперь посмотрим в IDA на результат дизассемблирования.
mainproc near ; CODE XREF: __scrt_common_main_seh+107↓p; DATA XREF: .pdаta:0000000140004018↓o; Объявляем две локальные переменные,; но почему две, если в исходном коде объявлена только одна?var_18= dword ptr -18hvar_14= dword ptr -14h; Резервируем место для локальных переменныхsub rsp, 38h; Инициализируем локальные переменные:; var_14 присваиваем значение 0х666, следовательно, это переменная amov [rsp+38h+var_14], 666hmov eax, [rsp+38h+var_14]Переменной var_18 присваиваем это же значение. Обрати внимание: ее создает оператор switch для собственных нужд. Значит, мы определили, для чего в программе объявлена вторая локальная переменная! Она нужна для хранения первоначального значения. Таким образом, даже если значение сравниваемой переменной var_14 в каком‑то ответвлении CASE будет изменено, это не повлияет на результат выборов, поскольку значение переменной var_18 не поменяется!
mov [rsp+38h+var_18], eax; Сравниваем значение var_18 с нулемcmp [rsp+38h+var_18], 0; Если сравнение успешно, переходим в блок кода, выводящий в консоль "a == 0"; Этот код получен трансляцией ветки case 0: printf("a == 0");; Иначе продолжаем выполнениеjzshort loc_140001115; Сравниваем значение var_18 с 1cmp [rsp+38h+var_18], 1; В случае успеха прыгаем внутрь блока кода для вывода "a == 1"; Этот код получен трансляцией ветки case 1: printf("a == 1");; Иначе продолжаем выполнениеjzshort loc_140001123; Сравниваем значение var_18 с 2cmp [rsp+38h+var_18], 2; В случае равенства выводим "a == 2"; Этот код получен трансляцией ветки case 2: printf("a == 2");; Иначе продолжаем выполнениеjzshort loc_140001131; Сравниваем var_18 и 0x666cmp [rsp+38h+var_18], 666h; Если равно, выводим "a == 666h"; Этот код получен трансляцией ветки case 0x666: printf("a == 666h");jzshort loc_14000113FЕсли мы досюда добрались, значит, ни одно условие не сработало, поэтому выполняем дефолтное действие: делаем безусловный переход в блок кода для вывода строчки Default.
Этот код получен трансляцией ветки default: :
jmp short loc_14000114D; ------------------------------------------------loc_140001115:; CODE XREF: main+19↑j; printf("a == 0");lea rcx, _Format ; "a == 0"call printfА вот этот безусловный переход, выносящий управление за пределы switch — в конец программы, есть оператор break, находящийся в конце каждой ветки. Если бы его не было, то начали бы выполняться все остальные ветки case, независимо от того, к какому значению var_18 они принадлежат!
jmp short loc_140001159 ; break; ------------------------------------------------loc_140001123:; CODE XREF: main+20↑j; printf("a == 1");lea rcx, aA1; "a == 1"call printfjmp short loc_140001159 ; break; ------------------------------------------------loc_140001131:; CODE XREF: main+27↑j; printf("a == 2");lea rcx, aA2; "a == 2"call printfjmp short loc_140001159 ; break; ------------------------------------------------loc_14000113F:; CODE XREF: main+31↑j; printf("a == 666h");lea rcx, aA666h ; "a == 666h"call printfjmp short loc_140001159 ; break; ------------------------------------------------loc_14000114D:; CODE XREF: main+33↑j; printf("Default");lea rcx, aDefault ; "Default"call printfloc_140001159: ; Конец SWITCH ; CODE XREF: main+41↑j; main+4F↑j ...; Возвращаем 0xor eax, eax; Восстанавливаем стекadd rsp, 38hretnmainendpВыглядит довольно прямолинейно. Дизассемблерный листинг можно условно разделить на две части: первая часть – сам оператор выбора, откуда каждое условие передает управление во вторую часть — конкретную ветку, соответствующую этому условию.
Для сравнения взглянем, какой код построит C++Builder 10 на основе этой же программы:
public mainmainproc near ; DATA XREF: __acrtused+29↑o; Как много локальных переменных!var_38= dword ptr -38hvar_34= dword ptr -34hvar_30= dword ptr -30hvar_2C= dword ptr -2Chvar_28= dword ptr -28hvar_24= dword ptr -24hvar_20= dword ptr -20hvar_1C= dword ptr -1Chvar_18= dword ptr -18hvar_14= dword ptr -14hvar_10= qword ptr -10hvar_8= dword ptr -8var_4= dword ptr -4; Открываем кадр стекаpush rbp; Резервируем место для локальных переменныхsub rsp, 60h; В RBP сохраняем указатель на дно стекаlea rbp, [rsp+60h]; Инициализируем локальные переменные:mov [rbp+var_4], 0mov [rbp+var_8], ecxmov [rbp+var_10], rdx; var_14 присваиваем значение 0х666, следовательно, это переменная amov [rbp+var_14], 666h; В ECX помещаем значение var_14mov ecx, [rbp+var_14]; Следующим элегантным образом сравниваем значение var_14 с нулемtest ecx, ecxКоманда TEST не меняет значение операндов, поэтому присваиваем переменной var_18 значение 0х666. Выходит, var_18 — автоматическая переменная, созданная switch для своей работы, чтобы при изменении var_14 внутри какой‑либо ветки кода это не повлияло на дальнейший выбор пути выполнения.
mov [rbp+var_18], ecx
|
|
|