Chapter_07 (1110559), страница 4
Текст из файла (страница 4)
Этой директивой программист уведомляет программу Ассемблеро том, что он (программист) гарантирует следующее: во время счёта программы сегментные регистры будут указывать на описанные в программе сегменты, как это показано на рис. 7.2 (хотя порядоксегментов в памяти и не обязан быть именно таким). Заметьте, что сама директива assume не меняет (не устанавливает) значение ни одного сегментного регистра, подробно про неё необходимо обязательно прочитать в учебнике [5].SSStack segment stackDSData segmentCSCode segmentРис. 7.2. Требуемые значения сегментных регистровво время счёта нашей программы.Заметим, что сегментные регистры SS и CS должны быть загружены перед выполнением самойпервой команды нашей программы. Ясно, что сама наша программа этого сделать не в состоянии,так как для этого необходимо выполнить хотя бы одну команду, что требует доступа к сегменту кода, и, в свою очередь, уже установленного на этот сегмент регистра CS.
Получается замкнутый круг,и единственным решением будет попросить какую-то другую программу загрузить значения этихрегистров, перед началом счёта нашей программы. Как мы потом увидим, это будет делать специальная служебная программа, которая называется загрузчиком, и нам, таким образом, об этом нестоит беспокоиться.1Первые две команды нашей программы загружают значение сегментного регистра DS, иначе невозможно работать с данными их этого сегмента. В младшей модели для этого необходимы именнодве команды, так как одна команда, которую часто пишут нерадивые учащиеся, имела бы несуществующий формат:mov ds,data; формат SR,i16 такого формата нет!Пусть, например, при счёте нашей программы сегмент данных будет располагаться, начиная садреса 10000010 оперативной памяти.
Тогда команда1Точнее, в тексте программы нам необходимо только как-то задать значения, которые надо загрузить в этирегистры, что для этого надо сделать мы скоро узнаем, а затем ещё раз строго определим это при описании работы загрузчика.10mov ax,dataбудет во время счёта иметь видmov ax,6250 ; 6250=100000 div 16; формат r16,i16Эта команда заносит на сегментный регистр данных адрес начала сегмента с именем data, делённый на 16, только после этого мы можем читать и писать данные в этот сегмент.Следующим предложением нашей программы является макрокомандаinint A; макрокоманда ввода целого числакоторая вводит значение целого числа в переменную A.Далее начнём непосредственное вычисление правой части оператора присваивания.
Задача усложняется тем, что величины A и B имеют разную длину и непосредственно складывать их нельзя.Приходится командамиmov al,B ; al := Bcbw; ax := длинное Bпреобразовать короткое знаковое целое число B, которое мы считали в регистр al, в длинное целоена регистре ax. Далее вычисляется значение выражения (A+B)2 и можно приступать к выполнениюделения. Так как делитель является длинным целым числом (мы поместили это число на регистр cx),то необходимо применить операцию длинного деления, для чего делимое (число 1234 на регистреax) командойcwdпреобразуем в сверхдлинное целое и помещаем на два регистра <dx,ax>. Вот теперь всё готово длякоманды целочисленного деленияidiv cx; ax := 1234 div (A+B)2 , dx := 1234 mod (A+B)2Далее мы аналогичным образом производим длинное деление значения вычисленного выражения 2*A–1234 div (A+B)2 на число 7, присваиваем остаток от деления (он в регистре dx) переменной X и выводим на экран значение этой переменной по макрокомандеoutint Xкоторая эквивалентна оператору процедуры Write(X) языка Паскаль.
Последним предложением всегменте кода является макрокомандаfinishЭта макрокоманда заканчивает выполнение нашей программы, она эквивалентна выходу программына Паскале на конечный end.Затем следует директива конца сегмента кодаcode endsИ, наконец, директиваend startзаканчивает описание всего модуля на Ассемблере. Обратите внимание на параметр этой директивы– метку start.
Она указывает входную точку программы, т.е. первую выполняемую команду нашей программы. Явное задание входной точки позволяет начать выполнения сегмента команд с любого места, а не обязательно с начала этого сегмента. Необходимость такого указания следует и изтого, что в программе могут быть несколько сегментов кода.Сделаем теперь важные замечания к нашей программе. Во-первых, мы не проверяли, что команды сложения и вычитания дают правильный результат (для этого, как мы знаем, после выполненияэтих команд нам было бы необходимо проверить флаг переполнения OF, т.к.
наши целые числа мысчитаем знаковыми). Во-вторых, команда длинного умножения располагает свой результат в двухрегистрах <dx,ax>, а в нашей программе мы брали результат произведения из регистра ax, предполагая, что на регистре dx находятся только незначащие цифры произведения. По-хорошему надобыло бы после команды умножения проверить условие, что флаги OF=CF=0, это гарантирует, что вdx содержаться только нулевые биты, если ax ≥ 0, и только двоичные "1", если ax < 0. Другимисловами, все биты в регистре dx должны совпадать со старшим битом в регистре ax, для знаковыхчисел это и есть признак того, что в регистре dx содержится незначащая часть произведения.
И, наконец, мы не проверили, что не производим деления на ноль (для нашего случае, что введённое значение A<>8). В наших учебных программах мы чаще всего не будем делать таких проверок, но в11"настоящих" программах, которые Вы будете создавать на компьютерах, эти проверки являются обязательными.Продолжая знакомство с языком Ассемблера, решим следующую задачу. Напишем фрагментпрограммы, в котором увеличивается на единицу целое число, расположенное в байте оперативнойпамяти с десятичным адресом 23456710.Мы уже знаем, что запись в любой байт памяти возможна только тогда, когда этот байт располагается в каком-либо сегменте.
Сделаем, например, так, чтобы наш байт располагался в том сегментеданных, на который указывает регистр DS. Главное здесь – не путать сегменты данных, которые мыописываем в программе на Ассемблере, с активными сегментами, на начала которых установленысегментные регистры.Описываемые в программе сегменты обычно размещаются загрузчиком на свободных участкахоперативной памяти, и, как правило, при написании текста программы их будущее месторасположение неизвестно.1 Однако ничто не мешает нам в процессе счёта программы любой участок оперативной памяти сделать "временным" сегментом, установив на него какой-либо сегментный регистр. Такмы и сделаем для решения нашей задачи, установив сегментный регистр DS на начало ближайшейучастка памяти, в котором будет находиться наш байт с адресом 23456710.
Так как в сегментныйрегистр загружается адрес начала сегмента, делённый на 16, то нужное нам значение сегментногорегистра можно вычислить по формуле:DS := 23456710 div 16 = 1466010При этом адрес A нашего байта в сегменте (его смещение от начала сегмента) вычисляется поформуле: A := 23456710 mod 16 = 7. Таким образом, для решения нашей задачи можно предложить следующий фрагмент программы:mov ax,14660mov ds,ax; Начало сегментаinc byte ptr ds:[7]В последней команде, увеличивающей значение нужного байта на единицу, нам пришлось явнозадать размер операнда с помощью операции byte ptr. Эта операция Ассемблера предписываетсчитать расположенный в памяти операнд команды (в нашем случае это команда увеличения операнда на единицу inc), имеющим длину один байт.
Дело в том, что по внешнему виду операндаэтой команды ds:[7] программа Ассемблера не в состоянии определить, что подлежит увеличениюна единицу: байт или слово (а это две разные команды машины, отличающиеся битом размераоперанда w). Для большинства команд, если Ассемблер не может определить в них длину операндов,он фиксирует ошибку в тексте программы, и она не будет запускаться на счёт.Теперь, после изучения арифметических операций, перейдём к рассмотрению команд переходов, которые понадобятся нам для программирования условных операторов и циклов. Напомним,что после изучения нашего курса мы должны уметь отображать на Ассемблер большинство конструкций языка Паскаль.7.6.