Невозможно отучить людей изучать самые ненужные предметы.
Введение в 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 -18h
var_14= dword ptr -14h
; Резервируем место для локальных переменных
sub rsp, 38h
; Инициализируем локальные переменные:
; var_14 присваиваем значение 0х666, следовательно, это переменная a
mov [rsp+38h+var_14], 666h
mov 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 с 1
cmp [rsp+38h+var_18], 1
; В случае успеха прыгаем внутрь блока кода для вывода "a == 1"
; Этот код получен трансляцией ветки case 1: printf("a == 1");
; Иначе продолжаем выполнение
jzshort loc_140001123
; Сравниваем значение var_18 с 2
cmp [rsp+38h+var_18], 2
; В случае равенства выводим "a == 2"
; Этот код получен трансляцией ветки case 2: printf("a == 2");
; Иначе продолжаем выполнение
jzshort loc_140001131
; Сравниваем var_18 и 0x666
cmp [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 printf
jmp short loc_140001159 ; break
; ------------------------------------------------
loc_140001131:; CODE XREF: main+27↑j
; printf("a == 2");
lea rcx, aA2; "a == 2"
call printf
jmp short loc_140001159 ; break
; ------------------------------------------------
loc_14000113F:; CODE XREF: main+31↑j
; printf("a == 666h");
lea rcx, aA666h ; "a == 666h"
call printf
jmp short loc_140001159 ; break
; ------------------------------------------------
loc_14000114D:; CODE XREF: main+33↑j
; printf("Default");
lea rcx, aDefault ; "Default"
call printf
loc_140001159: ; Конец SWITCH ; CODE XREF: main+41↑j
; main+4F↑j ...
; Возвращаем 0
xor eax, eax
; Восстанавливаем стек
add rsp, 38h
retn
mainendp
Выглядит довольно прямолинейно. Дизассемблерный листинг можно условно разделить на две части: первая часть – сам оператор выбора, откуда каждое условие передает управление во вторую часть — конкретную ветку, соответствующую этому условию.
Для сравнения взглянем, какой код построит C++Builder 10 на основе этой же программы:
public main
mainproc near ; DATA XREF: __acrtused+29↑o
; Как много локальных переменных!
var_38= dword ptr -38h
var_34= dword ptr -34h
var_30= dword ptr -30h
var_2C= dword ptr -2Ch
var_28= dword ptr -28h
var_24= dword ptr -24h
var_20= dword ptr -20h
var_1C= dword ptr -1Ch
var_18= dword ptr -18h
var_14= dword ptr -14h
var_10= qword ptr -10h
var_8= dword ptr -8
var_4= dword ptr -4
; Открываем кадр стека
push rbp
; Резервируем место для локальных переменных
sub rsp, 60h
; В RBP сохраняем указатель на дно стека
lea rbp, [rsp+60h]
; Инициализируем локальные переменные:
mov [rbp+var_4], 0
mov [rbp+var_8], ecx
mov [rbp+var_10], rdx
; var_14 присваиваем значение 0х666, следовательно, это переменная a
mov [rbp+var_14], 666h
; В ECX помещаем значение var_14
mov ecx, [rbp+var_14]
; Следующим элегантным образом сравниваем значение var_14 с нулем
test ecx, ecx
Команда TEST
не меняет значение операндов, поэтому присваиваем переменной var_18
значение 0х666
. Выходит, var_18
— автоматическая переменная, созданная switch
для своей работы, чтобы при изменении var_14
внутри какой‑либо ветки кода это не повлияло на дальнейший выбор пути выполнения.
mov [rbp+var_18], ecx
|
|