Имитация конструкции switch..case

Последнее обновление: 18.07.2023

В ряде языков высокого уровня есть конструкция switch..case, которая последовательно сравнивает некоторое выражение с набором значений:

switch(выражение){
    case значение_1: действия_1;
    case значение_2: действия_2;
    case значение_3: действия_3;
    default: действия_по_умолчанию;
}

Если найдено совпадение, то выполняются инструкции из соотвествующего блока case

Общий алгоритм данной конструкции может быть следующим:

  1. С помощью инструкции CMP сравниваем значение регистра с некоторым значением.

  2. Если сравнение оказалось НЕ верным, то переходим к следующей метке, которая сравнивает значение регистра с новым значением.

    Если сравнение оказалось верным, то выполняем определенные действия и затем переходим к метке завершения конструкции.

  3. Если ни одно из сравнений в инструкциях CMP НЕ является верным, выполняем действия по умолчанию.

Пример реализации:

// METANIT.COM. Пример программы с условной конструкцией аля switch..case
.global _start
 
_start: 
    mov x1, #2              // помещаем в x1 сравниваемое значение - число 2
 
_case_1: 
    cmp x1, #1         // сравниваем значение из x1 с первым значением - числом 1
    b.ne _case_2            // если равенство x1 == 1 НЕ верно, переход к метке _case_2
    mov x0, #11              // помещаем в регистр x0 в качестве кода возврата число 1
    b _endswitch            // переход в конец конструкции
 
_case_2: 
    cmp x1, #2         // сравниваем значение из x1 с первым значением - числом 2
    b.ne _case_3            // если равенство x1 == 2 НЕ верно, переход к метке _case_3
    mov x0, #22              // помещаем в регистр x0 в качестве кода возврата число 2
    b _endswitch            // переход в конец конструкции
 
_case_3: 
    cmp x1, #3         // сравниваем значение из x1 с первым значением - числом 3
    b.ne _default           // если равенство x1 == 3 НЕ верно, переход к метке _default
    mov x0, #33              // помещаем в регистр x0 в качестве кода возврата число 3
    b _endswitch            // переход в конец конструкции
 
_default: 
    mov x0, #10      // если предыдущие сравнения НЕ верны, помещаем в регистр x0 число 10 (значение по умолчанию)
 
_endswitch: //выходим из конструкции switch
    mov x8, #93         // номер функции Linux для выхода из программы - 93
    svc 0               // вызываем функцию и выходим из программы

Здесь значение регистра X1 последовательно сравнивается с тремя значениями, и в зависимости от результатов сравнения в регистр Х0 помещается то или иное значение.

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

Оптимизация

Приведенный выше пример довольно прост, но имеет большой недостаток - при большом количестве меток потребуется некоторое время чтобы дойти до последней метки. Например, если число равно 10, то будет выполняться кода на метке default. Но чтобы дойти до нее надо пройти все предыдущие метки и инструкции сравнения. При большом количестве переходов и меток это несколько снижает производительность. И здесь есть различные возможности оптимизации программы.

Если сравниваемые значения располагаются последовательно, как примере выше - 1, 2, 3, то эффективнее определить набор меток, к которым происходит переход при помощи косвенной адресации:

// METANIT.COM. Пример программы с условной конструкцией аля switch..case
.global _start
 
_start: 
    mov x1, #2              // помещаем в x1 сравниваемое значение - число 2

    cmp x1, 1
    b.lt default      // если меньше 1
    cmp x1, 3
    b.gt default      // если больше 3

    ldr x2, labels      // загружаем все метки
    add x2, x2, x1, lsl #3  // X2 = X2 + X1 * 8
    sub x2, x2, #8          // X2 = X2 - 8
    br x2               // переход по адресу из Х2

case_1: 
    mov x0, #11              // помещаем в регистр x0 в качестве кода возврата число 1
    b endswitch            // переход в конец конструкции
 
case_2: 
    mov x0, #22              // помещаем в регистр x0 в качестве кода возврата число 2
    b endswitch            // переход в конец конструкции
 
case_3: 
    mov x0, #33              // помещаем в регистр x0 в качестве кода возврата число 3
    b endswitch            // переход в конец конструкции
 
default: 
    mov x0, #10      // если предыдущие сравнения НЕ верны, помещаем в регистр x0 число 10 (значение по умолчанию)
 
endswitch: //выходим из конструкции switch
    mov x8, #93         // номер функции Linux для выхода из программы - 93
    svc 0               // вызываем функцию и выходим из программы
.data
labels: .quad case_1, case_2, case_3     // адреса меток

Здесь опять же сравниваем с теми же числами - 1, 2, 3. Вначале проверяем попадает ли число в этот диапазон - если оно меньше 1 и больше 3, то переходим к метке default. Для остальных переходов определяем специальную переменную - labels, которая хранит адреса трех меток.

Адрес этой переменной загружается в регистр X2. И далее устанавливаем регистр Х2 на адрес нужной метки, которая соответствует числу из Х1, и переходим к ней:

ldr x2, labels      // загружаем все метки
add x2, x2, x1, lsl #3  // X2 = X2 + X1 * 8
sub x2, x2, #8          // X2 = X2 - 8
br x2

То есть X2 представляет адрес переменной labels. Фактически это адрес первой метки, которая должна выполняться, если Х1==1. Чтобы перейти к метке, которая соответствует Х1, необходимо выполнить некоторые вычисления: X1*8-8. Адрес в 64-разрядной системе занимает 8 байт. Соответственно каждая метка располагается на расстоянии 8 байт от предыдущей. Например, адрес первой метки X2+0*8, адрес второй метки Х2 + 1*8, адрес третьей метки Х2 + 2 * 8 и так далее. Поскольку первая метка должна срабатывать, когда Х1 ==1, то для установки адреса применяем выражение X2 + X1 * 8-8, которое разбивается на две инструкции: ADD и SUB.

Но что, если нам надо сравнить с числами, которые идут не попорядку, например, 1, 2, 4, 8? Здесь между числами небольшой разбор - от 1 до 8, и мы могли бы сделать так:

.global _start
 
_start: 
    mov x1, #4              // помещаем в x1 сравниваемое значение - число 4

    cmp x1, 1
    b.lt default      // если меньше 1
    cmp x1, 8
    b.gt default      // если больше 8

    ldr x2, labels      // загружаем все метки
    add x2, x2, x1, lsl #3  // X2 = X2 + X1 * 8
    sub x2, x2, #8          // X2 = X2 - 8
    br x2

case_1:
    mov x0, #12     
    b endswitch  
case_2:
    mov x0, #23     
    b endswitch  
case_4:
    mov x0, #45 
    b endswitch
case_8:
    mov x0, #89 
    b endswitch
default: 
    mov x0, #10      // действия по умолчанию, если предыдущие равенства не верны
 
endswitch: //выходим из конструкции switch
    mov x8, #93         // номер функции Linux для выхода из программы - 93
    svc 0               // вызываем функцию и выходим из программы
.data
labels: .quad case_1, case_2, default, case_4 
         .quad    default, default, default, case_8      // адреса меток

Здесь все то же самое, только теперь адресов меток у нас 8, но 4 из них - адреса метки default. То есть если у нас будут для сравнения числа 3, 5, 6, 7, то для них будет выбираться метка default.

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850