Учебный курс. Часть 26. Локальные переменные

Автор: xrnd | Рубрика: Учебный курс | 17-10-2010 | Распечатать запись Распечатать запись

До этой части учебного курса все переменные в наших программах были только глобальными — они создавались и инициализировались при запуске программы и к ним можно было обратиться из любой её части.

Локальные переменные используются для хранения промежуточных результатов во время выполнения процедуры. В отличие от глобальных, эти переменные являются временными и создаются при запуске процедуры. Для локальных переменных существует понятие области видимости — так называется область программы, в которой доступна переменная. Обычно в ассемблере область видимости ограничена процедурой, создавшей локальную переменную. Хотя возможны и более сложные варианты 😉

Создание локальных переменных

Чтобы создать локальные переменные в процедуре, необходимо выделить для них память. Эта память выделяется в стеке. Сделать это очень просто — достаточно вычесть из регистра SP значение, равное суммарному размеру всех локальных переменных в процедуре. Так как ширина стека равна 16 бит, то это значение должно быть кратно 2 байтам. При выходе из процедуры нужно восстановить указатель стека. Обычно это выполняется командой mov sp,bp (В bp сохраняется значение sp при входе в процедуру, как в случае с параметрами, передаваемыми через стек). Код процедуры с локальными переменными будет выглядеть следующим образом:

;Процедура с локальными переменными
myproc:
    push bp                 ;Сохранение BP
    mov bp,sp               ;Копирование указателя стека в BP
    sub sp,locals_size      ;Выделение памяти для локальных переменных
    ...
    mov sp,bp               ;Восстановление указателя стека
    pop bp                  ;Восстановление BP
    ret                     ;Возврат из процедуры

Код, выполняемый при входе в процедуру, называют также кодом пролога, а код, выполняемый при выходе, — кодом эпилога. После выполнения кода пролога стек будет иметь такой вид:

Область стека, включающая в себя параметры процедуры, адрес возврата, локальные переменные и сохранённые регистры, называется кадром или фреймом стека. Из рисунка понятно, что для обращения к локальным переменным внутри процедуры нужно использовать отрицательные смещения относительно регистра BP. Лучше всего показать это на примере 🙂

Пример процедуры с локальными переменными

Пример очень простой. Внутри процедуры создаются 2 локальных переменных: одна размером слово и вторая размером байт. Чтобы не нарушить стек, нужно выделить 4 байта (один байт не будет использоваться). Процедуре будет передаваться через стек один параметр — x (слово без знака). Она будет вычислять выражение 1234+x/10 и возвращать его в регистре AX. Числа 1234 и 10 будут нашими локальными переменными. Конечно, в данном случае можно обойтись и без локальных переменных, они здесь только для примера.

;Пример процедуры с двумя локальными переменными
;вход: параметр x в стеке
;выход: AX = вычисленное значение
simpleproc:
    push bp                 ;Сохранение BP
    mov bp,sp               ;Копирование указателя стека в BP
    sub sp,4                ;Выделение 4 байт для локальных переменных
 
    mov word[bp-4],1234     ;Инициализация первой локальной переменной
    mov byte[bp-2],10       ;Инициализация второй локальной переменной
 
    mov ax,[bp+4]           ;AX = x
    div byte[bp-2]          ;AL = x/10
    xor ah,ah               ;AX = x/10 
    add ax,[bp-4]           ;AX = 1234+x/10
 
    mov sp,bp               ;Восстановление указателя стека
    pop bp                  ;Восстановление BP
    ret 2                   ;Возврат из процедуры

После выполнения кода пролога первая локальная переменная будет находиться по адресу bp-4, а вторая по адресу bp-2. Обратите внимание, что стековые переменные должны быть явно инициализированы. Так как память выделяется в стеке, то изначально в них будет всякий мусор (а вовсе не нули). Структура стека при выполнении процедуры показана на рисунке:

Команды ENTER и LEAVE

В системе команд процессоров x86 существуют также специальные команды для работы с кадром стека процедуры: ENTER и LEAVE. Команда ENTER обычно размещается в начале процедуры. У неё два непосредственных операнда: первый операнд — размер памяти, выделяемой под локальные переменные, второй операнд — уровень вложенности. в нашем случае второй операнд будет равен 0. Тогда по действию команда будет аналогична трём следующим командам:

    push bp                 ;\
    mov bp,sp               ; > или enter locals_size,0
    sub sp,locals_size      ;/

Второй операнд этой команды позволяет организовывать вложенные области видимости, как в некоторых языках высокого уровня. В ассемблере эти возможности используются редко.

Команда LEAVE не имеет операндов и аналогична по действию двум командам:

    mov sp,bp               ;\
    pop bp                  ;/ или leave

А так будет выглядеть наша процедура, если использовать команды ENTER и LEAVE:

;Пример процедуры с двумя локальными переменными
;вход: параметр x в стеке
;выход: AX = вычисленное значение
simpleproc2:
    enter 4,0               ;Создание кадра стека
 
    mov word[bp-4],1234     ;Инициализация первой локальной переменной
    mov byte[bp-2],10       ;Инициализация второй локальной переменной
 
    mov ax,[bp+4]           ;AX = x
    div byte[bp-2]          ;AL = x/10
    xor ah,ah               ;AX = x/10
    add ax,[bp-4]           ;AX = 1234+x/10
 
    leave                   ;Освобождение памяти, восстановление BP
    ret 2                   ;Возврат из процедуры

Вроде кажется, что проще, но компиляторы такой вариант практически не используют. Почему? Дело в том, что команда ENTER на современных процессорах выполняется гораздо медленнее, чем пролог из 3-х команд. Самый быстрый вариант такой:

;Процедура с локальными переменными
myproc:
    push bp                 ;Сохранение BP
    mov bp,sp               ;Копирование указателя стека в BP
    sub sp,locals_size      ;Выделение памяти для локальных переменных
    ...
    leave                   ;Освобождение памяти, восстановление BP
    ret                     ;Возврат из процедуры

Упражнение

Напишите улучшенную процедуру для ввода десятичного числа (от 0 до 255) с консоли. Буфер для ввода строки должен быть локальным, то есть выделяться в стеке при вызове процедуры. Процедура должна вызываться без параметров и возвращать введённое число в регистре AL. Ваши результаты, а также вопросы можете писать в комментариях.

Следующая часть »

Комментарии: