В ряде языков высокого уровня есть конструкция switch..case
, которая последовательно сравнивает некоторое выражение с набором значений:
switch(выражение){ case значение_1: действия_1; case значение_2: действия_2; case значение_3: действия_3; default: действия_по_умолчанию; }
Если найдено совпадение, то выполняются инструкции из соотвествующего блока case
Общий алгоритм данной конструкции может быть следующим:
С помощью инструкции CMP
сравниваем значение регистра с некоторым значением.
Если сравнение оказалось НЕ верным, то переходим к следующей метке, которая сравнивает значение регистра с новым значением.
Если сравнение оказалось верным, то выполняем определенные действия и затем переходим к метке завершения конструкции.
Если ни одно из сравнений в инструкциях CMP
НЕ является верным, выполняем действия по умолчанию.
Пример реализации:
.data number dword 2 .code main proc mov eax, number ; сравниваемое значение cmp eax, 1 ; number == 1 jne case_2 ; если не равно, переходим к метке case_2 mov eax, 11 ; действия, если числа равны jmp end_switch ; выходим из конструкции case_2: cmp eax, 2 ; number == 2 jne case_2 ; если не равно, переходим к метке case_3 mov eax, 22 ; действия, если числа равны jmp end_switch ; выходим из конструкции case_3: cmp eax, 3 ; number == 3 jne default ; если не равно, переходим к мет mov eax, 33 ; действия, если числа равны jmp end_switch ; выходим из конструкции default: mov eax, 44 ; действия по умолчанию, если предыдущие равенства не верны end_switch: ; конец конструкции ret main endp end
Здесь значение переменной number последовательно сравнивается с тремя значениями, и в зависимости от результатов сравнения в регистр eax помещается то или иное значение.
Поскольку достижение последней метки требует большего времени, то сравнения с наиболее вероятными значениями следует помещать ближе к началу конструкции.
Приведенный выше пример довольно просто, однако имеет один большой недостаток - при большом количестве меток потребуется некоторое время чтобы дойти до последней метки. Например, если число равно 10, то будет выполняться кода на метке default. Но чтобы дойти до нее надо пройти все предыдущие метки и инструкции сравнения. При большом количестве переходов и меток это несколько снижает производительность. И здесь есть различные возможности оптимизации программы.
Если сравниваемые значения располагаются последовательно, как примере выше - 1, 2, 3, то эффективнее определить набор меток, к которым происходит переход при помощи косвенной адресации:
.data number dword 2 .code main proc mov eax, number ; сравниваемое значение cmp eax, 1 jb default ; если меньше 1 cmp eax, 3 ja default ; если больше 3 lea rcx, labels ; загружаем все метки jmp qword ptr [rcx][rax * 8 - 8] ; переходим к одной из меток labels qword case_1, case_2, case_3 ; адреса меток case_1: mov eax, 11 ; действия, если числа равны jmp end_switch ; выходим из конструкции case_2: mov eax, 22 ; действия, если числа равны jmp end_switch ; выходим из конструкции case_3: mov eax, 33 ; действия, если числа равны jmp end_switch ; выходим из конструкции default: mov eax, 44 ; действия по умолчанию, если предыдущие равенства не верны end_switch: ; конец конструкции ret main endp end
Здесь опять же сравниваем с теми же числами - 1, 2, 3. Вначале проверяем попадает ли число в этот диапазон - если оно меньше 1 и больше 3, то переходим к метке default. Для остальных переходов определяем специальную переменную - labels, которая хранит адреса трех меток. Важно, что эта переменная определена в таком месте процедуры, в где она точно не будет выполняться как инструкция - в данном случае перед переменной идет переход к одной из меток.
Адрес этой переменной загружается в регистр RCX. И далее на основе значения в регистре EAX вычисляем адрес и переходим к нему:
jmp qword ptr [rcx][rax * 8 - 8]
То есть [rcx]
представляет адрес переменной labels. Адрес первой метки это также [rcx]
или [rcx] + 0
. В RAX мы ожидаем число 1, 2 или 3.
Соответственно с помощью выражения [rax * 8 - 8]
мы можем вычислить смещение метки для определенного числа в EAX. Например, если в EAX число 2, то мы получим
[rcx][2*8-8]
или [rcx][8]
, то есть адрес метки case_2.
Но что, если нам надо сравнить с числами, которые идут не попорядку, например, 1, 2, 4, 8? Здесь между числами небольшой разбор - от 1 до 8, и мы могли бы сделать так:
.data number dword 2 .code main proc mov eax, number ; сравниваемое значение cmp eax, 1 jb default ; если меньше 1 cmp eax, 8 ja default ; если больше 8 lea rcx, labels ; загружаем все метки jmp qword ptr [rcx][rax * 8 - 8] labels qword case_1, case_2, default, case_4, default, default, default, case_8 ; адреса меток case_1: mov eax, 12 jmp end_switch case_2: mov eax, 23 jmp end_switch case_4: mov eax, 45 jmp end_switch case_8: mov eax, 89 jmp end_switch default: mov eax, 101 ; действия по умолчанию, если предыдущие равенства не верны end_switch: ; конец конструкции ret main endp end
Здесь все то же самое, только теперь адресов меток у нас 8, но 4 из них - адреса метки default. То есть если у нас будут для сравнения числа 3, 5, 6, 7, то для них будет выбираться метка default.
Хотя в примере выше сравниваемые значение не идут по порядку, но все равно находятся довольно близко - в пределах первой восьмерки. Что делать, если разброс значений гораздо больше, например, 1, 2, 10, 11, 100, 101, 103, 1000, 1001? В этом случае мы можем сгруппировать близкие значения в отдельные группы и для каждой групп определить свой набор меток:
.data number dword 2 .code main proc mov eax, number ; сравниваемое значение cmp eax, 100 jb case0_11 cmp eax, 103 ja case1000_1001 lea rcx, labels100 jmp qword ptr [rcx][rax * 8 - 100 * 8] labels100 qword case_100, case_101, default, case_103 ; метки для 100, 101, 103 case0_11: cmp eax, 11 ja default ; если больше 10 lea rcx, labels0_11 jmp qword ptr [rcx][rax * 8] labels0_11 qword default, case_1, case_2, default, default, default, default, default, default, default, case_10, case_11 case1000_1001: cmp eax, 1000 jb default cmp eax, 1001 ja default lea rcx, labels1000 jmp qword ptr [rcx][rax * 8 - 1000 * 8] labels1000 qword case_1000, case_1001 case_1: mov eax, 21 jmp end_switch case_2: mov eax, 22 jmp end_switch case_10: mov eax, 23 jmp end_switch case_11: mov eax, 24 jmp end_switch case_100: mov eax, 25 jmp end_switch case_101: mov eax, 26 jmp end_switch case_103: mov eax, 26 jmp end_switch case_1000: mov eax, 27 jmp end_switch case_1001: mov eax, 28 jmp end_switch default: mov eax, 30 ; действия по умолчанию, если предыдущие равенства не верны end_switch: ; конец конструкции ret main endp end
Здесь все сравниваемые значения разбиты на три группы, для каждой из которых определен свой набор адресов меток. Данный способ в какой-то степени можно назвать бинарным поиском - среди групп находим группу со срединными значениями и остальные группы разбиваем на две части - одна со значениями меньше середины, а другая - со значениями больше середины.
С одной стороны, это ведет к усложнению программы и снижению ее читабельности, а с другой, увеличению скорости при большом количестве меток.