Реклама:

Наш следующий пример - vecprod.s - представляет собой небольшую программу, вычисляющую внутреннее произведение двух векторов: vecl и vec2. Ее код представлен в листинге В.З.

Листинг В.З. Программа vecprod.s

_EXIT =1 ! 1 определение значения _ЕХ1Т

_PRINTF =127 ! 2 определение значения _PRINTF

.SECT .TEXT ! 3 начало секции текста

inpstart: ! 4 определение метки inpstart

MOV BP.SP ! 5 сохранение SP в BP

PUSH vec2 ! 6 введение в стек адреса vec2

PUSH vecl ! введение в стек адреса vecl

MOV CX,vec2-vecl ! 8 СХ = число байтов в векторе

SHR СХ.1 ! 9 СХ = число слов в векторе

PUSH СХ ! 10 введение в стек числа слов

CALL vecmul

! 11

вызов vecmul

MOV (inprod),AX

! 12

перемещение AX

PUSH AX

! 13

введение в стек

   

выводимого результата

PUSH pfmt

! 14

введение в стек

   

адреса форматной строки

PUSH _PRINTF

! 15

введение в стек

   

кода функции PRINTF

SYS

! 16

вызов функции PRINTF

ADD SP.12

! 17

очистка стека

PUSH 0

! 18

введение в стек кода состояния

PUSH EXIT

! 19

введение в стек кода функции EXIT

SYS

! 20

вызов функции EXIT

vecmul:

! 21

начало vecmul(count, vecl, vec2)

PUSH BP

! 22

введение в стек значения BP

MOV BP.SP

! 23

копирование SP в BP

   

для доступа к аргументам

MOV CX,4(BP)

! 24

помещение счетчика в СХ

   

для управления циклом

MOV SI.6CBP)

! 25

SI = vecl

MOV DI.8CBP)

! 26

DI = vec2

PUSH 0

! 27

введение в стек значения 0

1: LODS

! 28

перемещение (SI) в АХ

MUL (DI)

! 29

умножение АХ на (DI)

ADD -2(BP).AX

! 30

прибавление АХ

   

к накопленному в памяти значению

ADD DI.2

! 31

приращение DI для указания

   

на следующий элемент

LOOP lb

! 32

если СХ > 0, возврат к метке lb

POP AX

! 33

выталкивание вершины стека в АХ

POP BP

! 34

восстановление BP

RET

! 35

возврат из подпрограмы

.SECT .DATA

! 36

начало секции данных

pfmt: .ASCIZ "Inner product is: £d\n"

! 37

определение строки

.ALIGN 2

! 38

принудительная четность адреса

vecl:.WORD 3,4,7,11,3

! 39

вектор 1

vec2:.WORD 2,6,3,1,0

! 40

вектор 2

.SECT .BSS

! 41

начало секции BSS

inprod: .SPACE2

! 42

выделение пространства для inprod

Первая часть этой программы призвана подготовить вызов функции vecmul; для этого SP сохраняется в BP, а затем адреса vec2 и vecl вводятся в стек, что обеспечивает функции vecmul возможность доступа к ним. Далее, в строке 8 длина вектора в байтах загружается в СХ. После смещения этого результата на 1 бит вправо (в строке 9) значение СХ выражает число слов в векторе, которое помещается в стек в строке 10. Вызов vecmul выполняется в строке 11.

Стоит, опять же, отметить, что аргументы подпрограмм помещаются в стек в обратном порядке, и делается это в целях соответствия принятому в языке С порядку вызова. Так, средствами С функцию vecmul можно вызвать следующим образом:

vecmul(count, vecl, vec2)

В ходе выполнения команды CALL адрес возврата помещается в стек. Путем трассировки можно определить, что этот адрес - 0x0011.

Первая команда в подпрограмме - PUSH. Она выполняется для указателя базы (BP) в строке 22. Значение BP сохраняется в связи с тем, что этот регистр понадобится для адресации аргументов и локальных переменных данной подпрограммы. Далее, в строке 23 в регистр BP копируется указатель стека; таким образом, новое значение указателя базы задает прежнее значение указателя стека.

После этого все готово к загрузке аргументов в регистры и резервированию пространства под локальную переменную. В следующих трех строках аргументы по одному извлекаются из стека и размещаются в регистре. Как вы помните, стек оптимизирован для хранения слов, значит, адреса должны быть четными. Адрес возврата следует сразу за прежним указателем базы, а потому обращение к нему производится как к 2(ВР). Следующим идет аргумент count - 4(ВР). Он загружается в регистр СХ в строке 24. В строках 25 и 26 векторы vecl и vec2 загружаются в регистры SI и DI, соответственно. Для сохранения промежуточного результата данной подпрограмме нужна одна локальная переменная с исходным значением 0. В связи с этим в строке 27 в стек вводится значение 0.

Состояние процессора непосредственно перед первым проходом цикла, начинающемся в строке 28, показано на рис. В.7. В узком окне в середине верхней части (справа от регистров) изображена область стека. На его дне находится адрес vec2 (0x0022); далее, в порядке восхождения, следуют адрес vecl (0x0018) и третий аргумент, выражающий число элементов в каждом векторе (0x0005). Затем указывается адрес возврата (0x0011). Цифра 1 слева от этого адреса свидетельствует о том, что он является адресом возврата, отстоящим на один уровень от основной программы. В окне под регистрами также показана цифра 1, но на этот раз она выражает символический адрес. Выше адреса возврата в стеке следуют старое значение BP (0x7fc0) и нуль, помещаемый в стек в строке 27. Стрелка, указывающая на это значение, отражает положение указателя стека (регистра SP). В окне справа от секции стека показан фрагмент текста программы; стрелка здесь указывает на следующую команду в порядке выполнения.

Вызов регистров команд и указателя

Рис. В.7. Содержимое окна трассера для программы уесргос1.8 при достижении строки 28, но до начала цикла

Теперь рассмотрим цикл, начинающийся в строке 28. Команда L0DS через регистр SI загружает слово памяти из сегмента данных в АХ. Так как флаг направления установлен, команда L0DS выполняется в автоинкрементном режиме, а значит, после ее завершения регистр SI указывает на следующий элемент vecl.

Чтобы представить этот механизм графически, запустите команду трассера

t88 vecprod

При появлении окна трассера введите следующую команду:

/vecmul+7b

Затем нажмите клавишу возврата каретки, установив тем самым контрольную точку в строке, содержащей команду L0DS. (Далее по тексту мы не будем напоминать о том, что после всех команд необходимо нажимать клавишу возврата каретки.) Введите команду

g

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

В строке 29 значение АХ умножается на исходный операнд. Слово в памяти, связанное с командой MUL, выбирается из сегмента данных с помощью регистра DI методом косвенной регистровой адресации. Неявным (не указанным в команде) целевым адресом команды MUL является комбинация регистров DX : АХ.

В строке 30 результат прибавляется к локальной переменной, расположенной в стеке по адресу -2(ВР). Так как команда MUL не выполняет автоматическое приращение своего операнда, это действие явно выполняется в строке 31. После этого регистр DI указывает на следующий элемент vec2.

Текущий этап работы программы завершается командой LOOP. Относительно значения регистра СХ выполняется отрицательное приращение, и если после этого оно остается положительным, программа переходит к локальной метке 1 в строке 28. Локальная метка 1Ь означает поиск ближайшей метки 1 в обратном направлении от текущей позиции. После завершения цикла подпрограмма выталкивает возвращаемое значение из стека в регистр АХ (строка 33), восстанавливает значение ВР (строка 34) и возвращается к вызывающей программе (строка 35).

После вызова выполнение основной программы возобновляется с помощью команды M0V (переход к строке 12). Эта команда открывает последовательность из пяти команд, направленную на вывод результата. Системный вызов printf построен по модели функции printf стандартной библиотеки программирования языка С. В строках 13-15 в стек помещаются 3 аргумента: целочисленное значение, которое предполагается вывести, адрес форматной строки (pfmt) и код функции printf (127). Форматная строка pfmt содержит символ #d, указывающий на то, что целочисленная переменная, необходимая для форматирования, является аргументом вызова printf.

В строке 17 стек очищается. Так как начало программы находится в строке 5, где указатель стека был сохранен в регистре указателя базы, для очистки стека с тем же успехом можно запустить команду

MOV SP,BP

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

Подпрограмму vecmul можно включать в другие программы. Если имя исходного файла vecprod.s поместить в командой строке перед именем другого исходного файла на языке ассемблера, последний сможет обращаться к подпрограмме умножения двух векторов фиксированной длины. Во избежание дублирования предварительно рекомендуется исключить определения констант EXIT и PRINTF. Если заголовочный файл syscalnr.h подсоединен, писать определения констант системных вызовов в других местах нет необходимости.

Регистры общего назначения-b || Оглавление || Отладка программы вывода массива