Информатика и информационные технологии: конспект лекций — страница 22 из 29

Перечислим еще некоторые особенности работы со стеком:

1) запись и чтение данных в стеке осуществляется в соответствии с принципом LIFO,

2) по мере записи данных в стек последний растет в сторону младших адресов. Эта особенность заложена в алгоритм команд работы со стеком;

3) при использовании регистров esp/sp и ebp/bp для адресации памяти ассемблер автоматически считает, что содержащиеся в нем значения представляют собой смещения относительно сегментного регистра ss.

В общем случае стек организован так, как показано на рисунке 23.

Рис. 23. Концептуальная схема организации стека


Для работы со стеком предназначены регистры SS, ESP/SP и ЕВР/ВР. Эти регистры используются комплексно, и каждый из них имеет свое функциональное назначение.

Регистр ESP/SP всегда указывает на вершину стека, т. е. содержит смещение, по которому в стек был занесен последний элемент. Команды работы со стеком неявно изменяют этот регистр так, чтобы он указывал всегда на последний записанный в стек элемент. Если стек пуст, то значение esp равно адресу последнего байта сегмента, выделенного под стек. При занесении элемента в стек процессор уменьшает значение регистра esp, а затем записывает элемент по адресу новой вершины. При извлечении данных из стека процессор копирует элемент, расположенный по адресу вершины, а затем увеличивает значение регистра указателя стека esp. Таким образом, получается, что стек растет вниз, в сторону уменьшения адресов.

Что делать, если нам необходимо получить доступ к элементам не на вершине, а внутри стека? Для этого применяют регистр ЕВР Регистр ЕВР – регистр указателя базы кадра стека.

Например, типичным приемом при входе в подпрограмму является передача нужных параметров путем записи их в стек. Если подпрограмма тоже активно работает со стеком, то доступ к этим параметрам становится проблематичным. Выход в том, чтобы после записи нужных данных в стек сохранить адрес вершины стека в указателе кадра (базы) стека – регистре ЕВР. Значение в ЕВР в дальнейшем можно использовать для доступа к переданным параметрам.

Начало стека расположено в старших адресах памяти. На рисунке 23 этот адрес обозначен парой ss: fffF. Смещение шТ приведено здесь условно. Реально это значение определяется величиной, которую программист задает при описании сегмента стека в своей программе.

Для организации работы со стеком существуют специальные команды записи и чтения.

1. push источник – запись значения источник в вершину стека.

Интерес представляет алгоритм работы этой команды, который включает следующие действия (рис. 24):

1) (sp) = (sp) – 2; значение sp уменьшается на 2;

2) значение из источника записывается по адресу, указываемому парой ss: sp.

Рис. 24. Принцип работы команды push


2. pop назначение – запись значения из вершины стека по месту, указанному операндом назначение. Значение при этом «снимается» с вершины стека. Алгоритм работы команды pop обратен алгоритму команды push (рис. 25):

1) запись содержимого вершины стека по месту, указанному операндом назначение;

2) (sp) = (sp) + 2; увеличение значения sp.

Рис. 25. Принцип работы команды pop


3. pusha – команда групповой записи в стек. По этой команде в стек последовательно записываются регистры ах, сх, dx, bx, sp, bp, si, di. Заметим, что записывается оригинальное содержимое sp, т. е. то, которое было до выдачи команды pusha (рис. 26).

Рис. 26. Принцип работы команды pusha


4. pushaw – почти синоним команды pusha В чем разница? Атрибут разрядности может принимать значение use16 или use32. Рассмотрим работу команд pusha и pushaw при каждом из этих атрибутов:

1) use16 – алгоритм работы pushaw аналогичен алгоритму pusha;

2) use32 – pushaw не изменяется (т. е. она нечувствительна к разрядности сегмента и всегда работает с регистрами размером в слово – ах, сх, dx, bx, sp, bp, si, di). Команда pusha чувствительна к установленной разрядности сегмента и при указании 32-разрядного сегмента работает с соответствующими 32-разрядными регистрами, т. е. еах, есх, edx, ebx, esp, ebp, esi, edi.

5. pushad – выполняется аналогично команде pusha, но есть некоторые особенности.

Следующие три команды выполняют действия, обратные вышеописанным командам:

1) рора;

2) popaw;

3) popad.

Группа команд, описанная ниже, позволяет сохранить в стеке регистр флагов и записать слово или двойное слово в стеке. Отметим, что перечисленные ниже команды – единственные в системе команд микропроцессора, которые позволяют получить доступ (и которые нуждаются в этом доступе) ко всему содержимому регистра флагов.

1. pushf – сохраняет регистр флагов в стеке.

Работа этой команды зависит от атрибута размера сегмента:

1) use 16 – в стек записывается регистр flags размером 2 байта;

2) use32 – в стек записывается регистр eflags размером 4 байта.

2. pushfw – сохранение в стеке регистра флагов размером в слово. Всегда работает как pushf с атрибутом use16.

3. pushfd – сохранение в стеке регистра флагов flags или eflags в зависимости от атрибута разрядности сегмента (т. е. то же, что и pushf).

Аналогично, следующие три команды выполняют действия, обратные рассмотренным выше операциям:

1) popf;

2) popftv;

3) popfd.

И в заключение отметим основные виды операции, когда использование стека практически неизбежно:

1) вызов подпрограмм;

2) временное сохранение значений регистров;

3) определение локальных переменных.

2. Арифметические команды

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

1) устройство для выполнения целочисленных операций;

2) устройство для выполнения операций с плавающей точкой.

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

Обзор группы арифметических команд и данных

Целочисленное вычислительное устройство поддерживает чуть больше десятка арифметических команд. На рисунке 27 приведена классификация команд этой группы.

Рис. 27. Классификация арифметических команд


Группа арифметических целочисленных команд работает с двумя типами чисел:

1) целыми двоичными числами. Числа могут иметь знаковый разряд или не иметь такового, т. е. быть числами со знаком или без знака;

2) целыми десятичными числами.

Рассмотрим машинные форматы, в которых хранятся эти типы данных.

Целые двоичные числа

Целое двоичное число с фиксированной точкой – это число, закодированное в двоичной системе счисления.

Размерность целого двоичного числа может составлять 8, 16 или 32 бит. Знак двоичного числа определяется тем, как интерпретируется старший бит в представлении числа. Это 7,15 или 31-й биты для чисел соответствующей размерности. При этом интересно то, что среди арифметических команд есть всего две команды, которые действительно учитывают этот старший разряд как знаковый, – это команды целочисленного умножения и деления imul и idiv. В остальных случаях ответственность за действия со знаковыми числами и, соответственно, со знаковым разрядом ложится на программиста. Диапазон значений двоичного числа зависит от его размера и трактовки старшего бита либо как старшего значащего бита числа, либо как бита знака числа (табл. 9).

Таблица 9. Диапазон значений двоичных чисел
Десятичные числа

Десятичные числа – специальный вид представления числовой информации, в основу которого положен принцип кодирования каждой десятичной цифры числа группой из четырех бит. При этом каждый байт числа содержит одну или две десятичные цифры в так называемом двоично-десятичном коде (BCD – Binary-Coded Decimal). Микропроцессор хранит BCD-числа в двух форматах (рис. 28):

1) упакованном формате. В этом формате каждый байт содержит две десятичные цифры. Десятичная цифра представляет собой двоичное значение в диапазоне от 0 до 9 размером 4 бита. При этом код старшей цифры числа занимает старшие 4 бита. Следовательно, диапазон представления десятичного упакованного числа в 1 байте составляет от 00 до 99;

2) неупакованном формате. В этом формате каждый байт содержит одну десятичную цифру в четырех младших битах. Старшие 4 бита имеют нулевое значение. Это так называемая зона. Следовательно, диапазон представления десятичного неупакованного числа в 1 байте составляет от 0 до 9.

Рис. 28. Представление BCD-чисел


Как описать двоично-десятичные числа в программе? Для этого можно использовать только две директивы описания и инициализации данных – db и dt. Возможность применения только этих директив для описания BCD-чисел обусловлена тем, что к таким числам также применим принцип «младший байт по младшему адресу», что очень удобно для их обработки. И вообще, при использовании такого типа данных как BCD-числа, порядок описания этих чисел в программе и алгоритм их обработки – это дело вкуса и личных пристрастий программиста. Это станет ясно после того, как мы ниже рассмотрим основы работы с BCD-числами.

Арифметические операции над целыми двоичными числами

Сложение двоичных чисел без знака

Микропроцессор выполняет сложение операндов по правилам сложения двоичных чисел. Проблем не возникает до тех пор, пока значение результата не превышает размерности поля операнда. Например, при сложении операндов размером в байт результат не должен превышать число 255. Если это происходит, то результат оказывается неверным. Рассмотрим, почему так происходит.