7_Язык Ассемблера (975804), страница 8
Текст из файла (страница 8)
Этим мудрёным термином обозначают совсемпростой процесс: сначала в сегменте данных размещается первая строка матрицы, сразу вслед на ней– вторая, и т.д. Для нашего примера в некотором сегменте надо каким-то образом зарезервировать2*N*M байт памяти. Процесс линеаризации усложняется, когда надо отобразить массивы большихразмерностей (например, четырёхмерный массив – совсем обычная вещь в задачах из области физики твёрдого тела или механики сплошной среды).
Подумайте, как надо сделать отображение такогомассива на линейную память. Кроме того, надо сказать, что некоторые языки программирования высокого уровня требуют другого способа линеаризации. Например, для языка Фортран нужно сначаларазместить в памяти сегмента первый столбец матрицы, потом второй и т.д.Теперь займёмся отображением структуры управления нашей программы на Паскале (а попросту говоря – её условных операторов и циклов) на язык Ассемблера. Сначала обратим внимание нато, что переменные i и j несут в нашей программе на Паскале двойную нагрузку: это одновременнои счётчики циклов, и индексы элементов массива. Такое совмещение функций упрощает пониманиепрограммы и делает её очень компактной по внешнему виду, но не проходит даром. Теперь, чтобыпо индексам элемента массива вычислить его адрес в сегменте данных, приходится выполнить достаточно сложные действия.
Например, адрес элемента X[i,j] компилятору с Паскаля приходитсявычислять так:Адрес(X[i,j]) = Адрес(X[1,1])+2*M*(i-1)+2*(j-1)Эту формулу легко понять, учитывая, что для Ассемблера матрица хранится в памяти компьютера по строкам (сначала первая строка, затем вторая и т.д.), и каждая строка имеет длину 2*M байт.Буквальное вычисление адресов элементов по приведённой выше формуле (а именно так чаще всегои делает Паскаль-машина) приводит к весьма неэффективной программе.
При отображении этихциклов на язык Ассемблера лучше всего разделить функции счётчика цикла и индекса элементов. Вкачестве счётчика будем использовать регистр cx (он и специализирован для этой цели), а адресаэлементов матрицы лучше хранить в индексных регистрах (bx, si и di). Исходя из этих соображений, можно так переписать программу на Паскале, предвидя её будущий перенос на Ассемблер.Const N=20; M=30; Sum: integer=0;VarX: array[1..N,1..M] of integer;cx,oldcx: integer; bx: ↑integer;Begin{ Ввод матрицы X }bx:=↑X[1,1]; {Так в Паскале нельзя}1for cx:=N downto 1 doif bx↑<0 then begin oldcx:=cx;for cx:=M downto 1 do beginSum:=Sum+bx↑; bx:=bx+2 {Так в Паскале нельзя}end;cx:=oldcxendelse bx:=bx+2*M {Так в Паскале нельзя}Теперь осталось переписать этот фрагмент программы на Ассемблере:1В языке Турбо-Паскаль вместо недопустимого оператора bx:=↑X[1,1]; можно использовать операторприсваивания bx:=@X[1,1]25NMoldcxDataXSumequ20equ30equdisegmentdwN*M dup (?)dw0.
. .Data ends. . .; Здесь ввод матрицы Xmovbx,offset X; Адрес X[1,1]movcx,NL1:cmpword ptr [bx],0jgeL3movoldcx,cxmovcx,ML2:movax,[bx]addSum,axaddbx,2loop L2movcx,oldcxjmpL4L3:addbx,2*M; На след. строкуL4:loop L1Приведённый пример очень хорошо иллюстрирует стиль мышления программиста на Ассемблере. Для доступа к элементам обрабатываемых данных применяются указатели (ссылочные переменные, значениями которых являются адреса), и используются операции над этими адресами (так называемая адресная арифметика). Получающиеся программы могут максимально эффективно учитывать все особенности архитектуры используемого компьютера. Заметим, что применение адресови адресной арифметики свойственно и некоторым языкам высокого уровня (например, языку С), который ориентирован на использование особенности машинной архитектуры для написания болееэффективных программ.Вернёмся теперь к описанию команд цикла. В языке Ассемблера есть и другие команды цикла,которые могут производить досрочный (до исчерпания счётчика цикла) выход из цикла с параметром.
Как и для команд условного перехода, для мнемонических имен некоторых из них существуютсинонимы, которые мы будем разделять в описании этих команд символом / (слэш).Командаloopz/loope Lвыполняется по схемеDec(CX); if (CX<>0) and (ZF=1) then goto L;А командаloopnz/loopne Lвыполняется по схемеDec(CX); if (CX<>0) and (ZF=0) then goto L;В этих командах необходимо учитывать, что операция Dec(CX) является частью команды цикла и не меняет флага ZF. Как видим, досрочный выход из таких циклов может произойти при соответствующих значениях флага нуля ZF.
Такие команды используются в основном при работе с массивами, для усвоения этого материала Вам необходимо изучить соответствующий раздел учебникапо Ассемблеру.Вопросы и упражнения1.2.3.Когда у программиста может появиться необходимость при написании своих программ использовать не более удобный для программирования язык высокого уровня, а перейти на языкнизкого уровня (Ассемблер)?Можно ли выполнять команды из сегмента данных?Для чего нужна команда (смены) префикса сегмента?264.5.6.7.8.9.10.11.12.13.14.15.В каких сегментах могут располагаться области данных для хранения переменных?Почему реализованы две макрокоманды для вывода знаковых (outint) и беззнаковых (outword) чисел, в то время, как макрокоманда ввода целого числа всего одна (inint)?Почему сегментные регистры CS и SS не могут быть загружены самим программистом в начале работы его программы?Почему в языке машины не реализована команда пересылки вида mov CS,op2 ?Почему для команд условного перехода оставлен только один формат близкого относительного короткого перехода?Для чего необходимы разные команды условного перехода после сравнения на больше илименьше знаковых и беззнаковых чисел?Что делать, если в команде условного перехода необходимо передать управление на метку,расположенную достаточно далеко от точки перехода?Чем отличается выполнение команды inc op1 от выполнения команды add op1,1 ?Почему для деления числа в формате слова на маленькие числа необходимо использовать команду длинного, а не короткого деления?Почему при реализации цикла на Ассемблере, если это возможно, надо выбирать цикл с постусловием, а не цикл с предусловием?Когда перед операндом формата i16 необходимо ставить явное задание длины операнда в виде word ptr ?Как работает оператор Ассемблера type ?277.8.
Работа со стекомПрежде, чем двигаться дальше в описании команд перехода, нам необходимо изучить понятиеаппаратного стека и рассмотреть команды машины для работы с этим стеком.Стеком в архитектуре нашего компьютера называется сегмент памяти, на начало которого указывает сегментный регистр SS (таким образом, стек в машине есть всегда). При работе программы врегистр SS можно последовательно загружать адреса начал разных сегментов, поэтому иногда говорят, что в программе несколько стеков. Однако в каждый момент стек только один – тот, на которыйсейчас указывает регистр SS. Именно его мы и будем далее иметь в виду.В нашей архитектуре стек хранится в сегменте памяти в перевёрнутом виде: начало сегмента (сменьшими адресами) является концом стека, а конец сегмента (с бóльшими адресами) – началом стека. Кроме начала и конца, у стека есть текущая позиция – вершина стека, её смещение от началасегмента стека всегда записано в регистре SP (Stack Pointer).
Следовательно, как мы уже знаем,физический адрес вершины стека можно получить по формуле Афиз = (SS*16 + SP)mod 220.Стек есть аппаратная реализация абстрактной структуры данных стек. В этой реализации ввершину стека можно записывать (и, соответственно, читать из неё) только машинные слова, команды чтение и запись в стек байтов в архитектуре рассматриваемого нами компьютера не предусмотрены. Это, конечно, не значит, что в стеке нельзя хранить байты, двойные слова и т.д., просто нетмашинных команд для записи в вершину стека и чтения из вершины стека данных этих форматов.Наличие стека не отменяет для нашего компьютера принципа Фон Неймана однородности памяти,поскольку одновременно стек является и просто сегментом оперативной памяти и, таким образом,возможен обмен данными с этой памятью с помощью обычных команд (например, команд пересылки mov).В соответствие с определением машинного стека последнее записанное в него слово будет храниться в вершине стека, и читаться из стека первым.
Это так называемое правило "последний пришёл – первый вышел" (английское сокращение LIFO – Last In First Out). Вообще говоря, это же правило можно записать и как "первый пришёл – последний вышел" (английское сокращение FILO –First In Last Out). В литературе встречаются оба этих правила и их сокращения. Будем изображатьстек в нашей архитектуре "растущим" снизу-вверх, от конца к началу сегмента стека. Как следствиеполучается, что конец стека фиксирован и будет расположен на рисунках снизу, а вершина двигаетсявверх (при записи в стек) и вниз (при чтении из стека).В любой момент времени регистр SP указывает на вершину стека – это последнее слово, записанное в стек.
Таким образом, регистр SP является специализированным регистром, на что указывает и его название (Stack Pointer), следовательно, хотя на нём и можно выполнять арифметические операции сложения и вычитания, но делать это следует только тогда, когда мы хотим изменитьположение вершины стека. Будем изображать стек, как показано на рис. 7.3.Начало стека SS →Вершина стека SP →Конец стека →SP для пустого стека →Рис. 7.3. Так мы будем изображать стек.На этом рисунке, как это реализовано в нашей архитектуре, стек растёт снизу-вверх, занятаячасть стека закрашена. В начале работы программы, когда стек пустой, регистр SP указывает на первое слово за концом стека.
Особым является случай, когда стек имеет максимальный размер 216байт, в этом случае значение регистра SP для пустого стека равно нулю, т.е. совпадает со значениемэтого регистра и для полного стека. Это происходит из-за того, что сегмент максимального размера,28как мы знаем, как бы склеен в кольцо. Именно поэтому стеки максимального размера использовать впрограммах не рекомендуется, так как будет затруднён контроль пустоты и переполнения стека.Обычно для резервирования памяти под стек на языке Ассемблера описывается специальныйсегмент стека.