- Код: Выделить всё
Сазонов Д.О.
Ассемблерные вставки на Turbo Pascal.
Особенности. Методика преподавания
Введение
"долог путь поучений, короток и успешен путь примеров!"
-Сенька, Компьютер в школе №10 (14) дек. 1999г
На рынке печатной продукции появляется все больше и больше различных учебников, описаний и справочников. Основной упор книжная индустрия делает на издание книг для пользователей: описание программ, описание ОС и т.д. И хотя по программированию тоже много книг, но в основном уделяется внимание языкам высокого уровня BASIC, PASCAL, C++, а также визуальным средам программирования Builder C++, Inprise Delphi и т.к. К сожалению не так много книг посвящается ассемблеру, а те которые есть в основном служат для знакомства с ним и не рассматривают профессиональное программирование на этом языке.
С появлением визуальных сред программирования о ассемблере немного забыли - и действительно в нем отпала необходимость. Ведь вряд ли кто будет писать программы под Windows на ассемблере. Для программ MSDOS в основном используется С++, а ассемблер используется как "ассемблерная вставка", повышающая производительность программы.
В этой работе я буду рассматривать ассемблер не как полноценный язык, а как дополнение к языку PASCAL. Таким образом мы сможем избавиться от большинства проблем связанным с языком ассемблера и в то же время узнать те преимущества с помощью которых ассемблер делает любой язык высокого уровня полноценным языком. Моей же второй целью написания этой работы является открытие новой точки зрения на ассемблер т.к. я не разу не видел описание ассемблера, как дополнения паскаля.
Основы.Организация памяти ПК
Рассмотрим общую структуру памяти персонального компьютера. Вся память у компьютера делится на три части: основная, дополнительная и расширенная. Первая часть - это первых 640кб память, вторая это промежуток 640-1024кб, и третья все что выше 1мб памяти. Обычно в MSDOS программы, которым достаточно использования первых двух частей памяти работают в так называемом реальном режиме.
Если же необходимо больше памяти и ресурсов программе (например Windows 95 и т.д.) программа переходи в так называемый защищенный режим, где ей доступна вся память целиком. В DOSе для работы в защищенном режиме обычно используется программа DOS4GW.EXE, а в Windows за памятью следит сама ОС.
Нас пока будут интересовать только два первых участка памяти.
Рассмотрим первый из них. Базовая память тоже делится на части. Полностью ее всю использовать программа не может. В начале ее хранятся различные переменный и таблица векторов прерываний, а также в нее загружается и сама MSDOS, программа поддержки русского шрифта и т.д. Так что использовать можно примерно 500кб-550кб, остальное занято системой. Остается открытым вопрос - как обращаться к этой памяти. Для обращения к ячейки памяти существует адрес для каждой ячейки памяти. Вообще память можно сравнить с большим городом, где каждый дом есть адрес памяти.
Тем не менее если мы на почте скажем просто номер дома - наша почта не дойдет наверняка, т.к. посыльный просто не будет знать в какой из тысячи или более домой с данным номером нести письмо. Для этого еще существуют и улицы. Мы указывает улицу и номер дома, тогда наше письмо дойдет до адресата. Так и в компьютере.
Разработчики решили, что вся память будет разделена на улицы, на каждой из которых будет 65535 домов (ячеек памяти). Улицы они назвали СЕГМЕНТАМИ, а номера домов СМЕЩЕНИЕМ (от начала сегмента) , и вместо названий тоже дали им номера.
Таким образом, чтобы получить или записать что-либо в ячейку памяти нам необходимо указать номер улицы и номер дома, иными словами СЕГМЕНТ: СМЕЩЕНИЕ (адрес записывается через двоеточие). На практике это выглядит так: 0000:0001 (нулевая улица, первый дом)
Представление данных
Все данные в компьютере представляются с помощью байтов. Для компьютера не существует не букв, ни дробных чисел, ни вообще ничего кроме байта. Таким образом каждой букве дали код, и чтобы представить букву необходимо записать ее код в ячейку. Но все равно для компьютера это будет всего лишь число, а вам уже придется решать, что означает это буква для вас.
Например, 65 для компьютера это просто число, а если рассматривать 65 как букву, то это буква "А". Соответственно в ячейке нельзя записать число большее чем 255. Поэтому большие числа записываются в двух и более ячейках. Коды команд также представляются с помощью чисел.
В данной части стоит обратить внимание на представление больших чисел. Допустим нам необходимо записать в память число 30000. Так как ПК не может записать такое большое число в одну ячейку- он сначала определяет сколько ячеек потребуется для представления такого числа. Для этого 30000 делится на 256. Если получилось число <256 - стало быть нужно 2 ячейки, иначе если число больше 65535, делим на 65535 и т.д. Итак при делении получилось 117.118. Далее целую часть умножаем на 256 и вычитаем из 30000. (подумайте сами зачем это надо). Получилось число 48. Итак, получили два числа 117 и 48. Первым запишется в ячейку последнее число т.е. 48, затем 117.
Вывод числа представляются в памяти делением на 256.
Рассмотрим еще пример. Необходимо представить число 123456 в памяти. Делим на 65536 - получили 1.8, (1 - первое число) умножим 65536 на 1.8 и вычтем из 123456 - получили 57920. Теперь делим это число на 256 (т.к. оно меньше 65536). Получили 226.25 (второе число). 57920-256*226=64 (третье число). Итак получились числа 64, 226, 1.
Машинный язык и язык ассемблера.
В чем же разница между машинным языком и языком ассемблера? Ведь когда не было ассемблера все программы писались на машинном языке. Но в конце концов писать программу на машинном языке очень трудно и тогда появился язык ассемблера. Само название "машинный язык" говорит само за себя - язык понятный для машины (компьютера). Как Вы уже, наверное, знаете компьютер все команды переводит в числа, да и понимает он только числа так вот машинный язык это набор чисел представляющих те или иные команды. Человеку же очень трудно запомнить какое число что обозначает, ну например число 40h означает что нужно увеличить регистр АX на 1, а 48h - уменьшить АХ на 1. Если набор команд процессора еще не очень большой (хотя бы 100 команд) еще можно запомнить что, какое число обозначает. А если команд 1000 или больше - это крайне тяжело. Тогда решили каждой команде дать свое имя и написать программу (транслятор) которая бы вместо словесных названий команд подставляла соответствующие числа.
Естественно названия командам давались по смыслу работы команды. Например inc ax - в переводе означает увеличить АХ, от английского incresize, а dec AX - уменьшить ах. Транслятор же подставляет вместо этих слов их коды на машинном языке - 40h и 48h. Итак ассемблером можно назвать такое представление команд машинного языка, где каждой команде соответствует уникальное имя, по смыслу подходящее логике команды.
Зачем нужен ассемблер?
Этот вопрос в последнее время задается все чаще и чаще. И действительно зачем, когда есть такие мощные языки как С++ или тот же самый PASCAL? На этих языкам можно писать и резидентные программы и драйверы мне также приходилось сталкиваться с компьютерными вирусами написанными на PASCAL и даже на BASIC. Но сколько бы не писали новых компиляторов языков высокого уровня никогда по оптимальности они не приблизятся к языку ассемблера. Всегда программа на С++ и PASCAL будет больше программы на ассемблере, и работать она будет дольше, хотя бы на несколько миллисекунд. Приведем небольшой пример, чтобы сравнить эффективность программы написанной на С++ и на ассемблере. Задача будет такой: необходимо на экране напечатать символ 'A'.
На С++ это можно сделать командой printf("%s","A");
Теперь скомпилирует эту программу на С++ и на языке ассемблера, затем посмотрим на размер скомпилированной программы.
На языке С++ размер получился 8804 байта, а на ассемблере всего 8 байт. Попробуем сделать по другому и будем компилировать программу в С++ и использованием ассемблера. Для этого надо включить опцию в меню Options ->Compiler ->Code Generation->
[x] Compile via assembler. С помощью этой опции С++ сначала преобразовывает программу по своему, а затем полученный код пропускает через стандартный компилятор ассемблера. Итак программа получилась размером 8980 байт, что еще больше предыдущего результата. Стало быть С++ создает не оптимальные коды. Попробуем оптимизировать код с помощью стандартного же оптимизатора С++. Меню Options->Compiler->Optimization. Далее выберем пункт Samllest Code и посмотрим на размер теперь. Ничего не изменилось и это лишь доказывает, что С++ не сможет сравнится с ассемблером. А как же встроенный ассемблер? Попробуем и это, тогда программа на С++ будет выглядеть так.
void main(void)
{ asm mov dl,'A'
asm mov ah,2
asm int 21h
}
Размер такой программы 6103 байта. Даже с использованием ассемблерной вставки С++ не может преобразовать программу, так чтобы ее размер хоть немного приблизился к размеру программы на ассемблере. Попробуем PASCAL. В паскале дело обстоит чуть лучше размер программы - 2208 байт, но он проигрывает по скорости С++.
Как видите оптимальную программу и по размеру и по скорости выполнения можно написать только на ассемблере. Именно по этому он и не заменим ни С++, ни PASCAL.
Тем не менее приходится мириться с размером программы, да и со скоростью ее выполнения тоже из-за затрат времени, на отладку программы на ассемблере. По этому сейчас в основном ассемблер существует лишь как дополнение к языкам высокого уровня в виде ассемблерных вставок. Таким образом программу можно писать на PASCAL или C++, а ту часть программы где требуется дополнительная скорость и производительность - на ассемблере.
Почему PASCAL + ассемблер?
При написании данной работы перед мной стоял выбор какой язык выбрать для описания ассемблерных вставок Pascal или С++. В конце концов мой выбор остановился на языке PASCAL. Во первых из-за того, что в С++ плохо организованы переходы в ассемблерных вставках, в вернее будет сказать, что в встроенном ассемблер С++ нет меток. Во вторых нельзя не отметить нестандартный способ передачи параметров функции я языке С++, т.е. соглашения о вызовах функций не соответствуют нормам (если так можно сказать) принятым в программировании. Ну и, конечно, надо отметить, что язык С++ не так известен, как язык PASCAL, и начинающему пользователю будет гораздо сложнее разобраться в ассемблерных вставках, если мы будем использовать еще и не знакомый язык программирования.
Рассмотрим преимущества изучения встроенного ассемблера PASCALя.
1. Все стандартные команды ассемблера присутствуют.
2. Чтобы посмотреть работу программы нам не надо делать объектный файл, а затем его преобразовывать в EXE.
3. Мы можем осуществить пошаговый просмотр работы программы (трассировку) с помощью встроенного отладчика (Debug)
4. Нам не надо использовать прерывания чтобы осуществить ввод и вывод символов. Для этого мы можем использовать write и read.
5. Оформление программы можно написать полностью на pascal, тем самым не надо отвлекаться на "разукрашивание" программы на ассемблере. (экономия труда)
6. Простая работа с типами данных
7. Использование watches
Но как всегда есть и недостатки.
1. PASCAL не поддерживает 32-х битные операции и регистры
2. В ассемблерных вставках необходимо учитывать специфику языка PASCAL
3. Программа получается во много раз больше чем на "чистом" ассемблере.
4. Необходимо следить за сегментом данных DS. Перед началом работы сохранять его, а потом восстанавливать.
Похоже самым сильным недостатком PASCAL является его неспособность поддержки 32-х битного режима. Но с этим можно справиться и далее я научу вас как это делать. В объектно-ориентированных языках (визуальных), таких как Delphi, включена 32-х битная поддержка ассемблерных команд. Хотя это и существенный недостаток, тем не менее PASCAL без ассемблерных вставок для программиста был бы бесцветным как черно-белая фотография.
Основные принципы языка
В отличии от языков высокого уровня ассемблер обладаем весьма скудным набором действий, тем не менее комбинируя эти действия вы можете получить богатейший набор средств для написания программ. По сути дела ассемблер может только пересылать значения из одного участка памяти в другой. Если вернуться к аналогии с улицами и домами представляя память ПК, то можно сказать что ассемблер является почтальоном, который переносит посылки из одного дома в другой. Посылками являются числа. Например в одном из домов почтальон берет число, он может прибавить к нему единицу или отнять, прежде чем доставить в другой дом. Причем он может нести только одну посылку. Бывают случаи когда нужно сложить вместе две посылки. Тогда уже необходимы два почтальона. В ассемблере всего не белее десятка почтальонов. И когда посылок появляется слишком много, то их относят на склад, называемый стеком. Для каждой запускаемой программы используется свой квартал- несколько улиц с определенными именами. Та улица, в домах которой хранится код программы, называется CS. Улица где находятся переменные, используемые программой и ее данные называется DS. А склад или стек (для каждой программы используется свой стек) называется SS.
Конечно же такие названия даны не спроста. CS- code segment (сегмент кода), DS - data segment (сегмент данных), SS - stack segment (сегмент стека). Почтальоны также имеют свои имена:
AX, BX, CX, DX - почтальоны которые могут забрать посылки сразу из двух домов (в байта), при этом первая посылка будет находится в AL, BL,CL, DL, а вторая в AH, BH, CH, DH соответственно.
Также существуют специальные почтальоны, которые могут обрабатывать целые улицы дом за домом по порядку - это SI и DI. Их нельзя разделить и они всегда переносят по 2 посылки сразу.
Чтобы определить на какой улице почтальону необходимо забрать посылку пишут название улицы и, через двоеточие почтальона. Например: CS:AX и т.д. В ассемблере эти почтальоны называются регистрами и являются по сути самым важным звеном языка.
Практически все действия в ассемблере связанны с пересылкой данных из одного места в другой. Но так как данные не могут сами «ходить» мы просим почтальона. Попросит же надо его «вежливо» - отчетливо объяснить что именно от него требуется. Для этого существуют различные команды. Рассмотрим основные из команд которые может обработать наш «почтальон».
MOV – отнести
ADD – сложить
SUB – вычесть
CP – сравнить значение
PUSH – отнести на склад (стек)
POP – забрать со склада (из стека).
JMP просто идти куда-либо (аналог команды GOTO)
INC – увеличить на 1 содержание посылки
DEC - уменьшить на 1 содержание посылки
LOOP – повторять некоторые действия столько раз сколько указано в регистре CX.
NEG – преобразует число в отрицательное (если оно положительное) и наоборот.
MUL – умножение содержимого посылки
DIV – деление содержимого посылки
AND – логическое «И»(см. алгебру логики)
OR – логическое «ИЛИ»(см. алгебру логики)
XOR – «ИСКЛЮЧАЮЩЕЕ ИЛИ»(см. алгебру логики).
Но даже из этого небольшого количества использоваться мы будем только самые основные:
MOV, ADD, SUB, CP, PUSH, POP, JMP, INC, DEC.
На самом деле в языке ассемблер очень много различных команд, но на них не стоит пока заострять внимание: как говорить всему свое время. В принципе данными 9 командами можно написать практически любую программу (хотя в машине Тьюринга было еще меньше команд).
Флаги
В PASCALе, как и в другом любом языке программирование встречаются много задач (да все практически) в которых необходимо делать сравнение. Сравниваются числа, проверяются условия и т.д. Например задачу поиска максимума не возможно решить без сравнения одного числа с другим. А задумывались ли вы как происходит само сравнение.
Например if a>b then ….
или if a>21 then…
Для нас такая запись очень даже понятна, а для машины? Нет – компьютер этого не понимает. Возможно вы удивитесь, но тем не менее PASCAL может сравнивать все только с НУЛЕМ!
Таким образом все эти операции в памяти приводятся к такому виду:
If (a-b)>0 then….
If (a-21)>0 then….
Но опять же, говоря о PASCAL и рассматривая более глубоко данную запись мы увидим, что компилятор приводит сравнение к такому виду:
If ((a-b)>0)=true then….
If ((a-21)>0)=true then….
Поскольку для ассемблера ноль, как число мало что значит. Он лишь может сказать «да – это так» (true) или «нет – это не так» (false). То есть «да»=true=1, «нет»=false=0. Чтобы сэкономить память в ассемблере есть еще один регистр называемый регистром флагов. В нем хранятся как раз проверки на различные случаи жизни, чтобы знать «так это» или «не так».
Существует много событий о которых автоматически сообщает процессор регистру флагов. В общем можно сказать, что в регистр флагов собирается вся информация о том, что произошло в компьютере. И в зависимости от события в нем «устанавливается» или «сбрасывается» бит, отвечающий за это событие.
Регистр флагов носит имя AF.
Это 16 битный регистр и причем у каждого его бита есть свое собственное название. Вот список наиболее важных из этих названий которые мы будем использовать далее.
ZF – бит отвечающий как раз за то был ли в предыдущей операции ноль.
CF – устанавливается если при сравнении одно число оказалось больше другого
SF – устанавливается в 1 если в результате последней арифметической операции получилось отрицательное число.
В командах ассемблера F опускается и пишут просто Z,C,S …
Или дописывают вначале букву N если хотят сказать, что нужен «не ноль», «меньшее число», «положительное число» т.е. NZ, NC, NS….
Итак программа по сравнению в общем виде на ассемблере будет выглядеть так:
Cmp a,b
Jnc …..
Машина же поймет это примерно так:
Sub a,b
{в зависимости от результата вычитания проверяется знак получившегося числа – S}
js ….. {переход}
Подводя итог кратко можно сказать так:
Флаг нуля Z– устанавливается в 1 если в рез. Арифметической операции получился ноль или при сравнении сравниваются одинаковые числа.
Флаг переноса C– устанавливается в 1 если при сравнении одно число было больше другого или в результате арифметического действия число получилось слишком большое (разряды числа выходят за пределы ячейки.
mov al,200
mov ah,200
add ah,al
Флаг знака S – устанавливается в 1 если в результате арифметической операции получилось отрицательное число.
mov al,20
mov ah,30
sub al,ah
Стек
Довольно часто встречаются ситуации, когда в программе задействованы уже все регистры, а данные все поступают и поступают. В таком случае регистры (почтальоны) просто не справляются со своей работой – просто напросто не остается свободных «рук» чтобы отнести ту или иную посылку адресату. Тогда поступают так: ту посылку доставка которой не является первостепенной относят на склад (стек), а затем забирают и относят туда куда надо. Аналогию можно провести со стопкой бумаги.
Допустим вы распечатываете документы и складываете все по мере поступления – таким образом самые последние листки остаются сверху. когда распечатка закончена вы забираете листы начиная с верхнего как с самого важного и потом только доходите до самого первого. Такая организация называется filo (first in last out) первым пришел – последним уйдешь. Также очень удобно на практике то, что программисту не надо заботиться о том по какому конкретно адресу будут храниться данные стека. Машина сама все сделает за вас. Итак дадим четкое определение стека:
DF: стек – это структура данных, в которой можно добавлять и удалять элементы данных; при этом доступен только последний добавленный элемент, и программа может получить его значение или удалить его из стека.
Стек реализуется в виде массива с двумя указателями – указателем на первый элемент (дно стека) и указателем на последний элемент (вершину стека); операции над стеком увеличивают или уменьшают указатель вершины стека.
Синтаксис команд
Разумеется просто так нельзя написать MOV (или еще что нибудь) – компьютер просто не поймет что конкретно нужно сделать, кроме того, что требуется что-то отнести куда-то. Мы говорим ему: «пойди неизвестно куда и принеси неизвестно что». Такое и человека запутает. По этому в большинстве команд используются также и имена регистров (почтальонов) или (и) адреса памяти куда нужно отнести посылку.
Таким образом запись MOV AX,BX компьютеру понятна – нужно в регистре АХ сделать копию ВХ. Или говорят, что значение ВХ пересылается в АХ. Или надо поместить в DX какое-то число, тогда напишем MOV DX,23 и т.д.
Обозначим регистр как R, число как N, память как М, а переменная PASCAL - V. Тогда весь допустимый синтаксис сводится к таблице.
MOV R,R
MOV R,N
MOV R,M
MOV M,R
MOV V,R
MOV R,V
ADD R,R
ADD V,R
ADD R,V
ADD R,N
ADD R,M
ADD M,R
SUB R,R SUB R,V SUB V,R
SUB R,N
SUB R,M
SUB M,R
CMP R,R
CMP R,N
CMP R,M
PUSH R
PUSH N
PUSH M
POP R
POP M
JMP N
JMP M
INC R
INC M
DEC R
DEC M
Таким образом мы можем сразу обозначить правила действия каждой команды и допустимые значения. Так, судя из таблицы, запись POP 34 – будет неверной потому, что 34 число т.е. N.
Где хранятся команды? Память.
Когда вы пишите программу она храниться на бумаге или быть может на распечатке, но когда она выполняется все команды должны быть в память компьютера (хотя желательно, чтобы и в памяти программиста отложилась их часть иначе потом забудете, что программа делает и зачем создавалась). Программируя на ассемблере программист сам должен позаботиться о том, в каком месте хранить ту или иную переменную или просто значение. Преимуществом же ассемблерной вставки является то, что об этом заботиться PASCAL и нам не придется ломать голову над вопросом где хранятся число и знать его адрес. Единственное о чем нам надо заботиться – это выделить эту память. Используем VAR.
VAR i:integer;
b:byte;
Напомню, что тип байт может использоваться только регистрами
AL, BL,CL, DL, AH, BH, CH, DH. А integer (или Word) – AX, CX, BX,DX ….
Помнить придется только это. И переменные заранее готовить соответствующего типа.
Команда пересылки данных
MOV
1. Дана переменная типа integer. Присвоить ей значение 10 с помощью ассемблера. Затем вывести на экран стандартным способом.
var a:integer;
begin
asm
mov bx,10
mov a,bx
end;
writeln(a);
end.
2. Даны две переменных а (тип integer) и b (тип byte). Присвоить b – значение а. Вывести на экран.
var a:integer;
b:byte;
begin
writeln('Введите a:');
readln(a);
asm
mov ax,a
mov bl,al
mov b,bl
end;
writeln(b);
end.
3. Даны две переменные a, b (тип byte). С клавиатуры вводятся их значения.В ассемблерной вставке поменять значения переменных местами. Далее вывести значения обоих переменных на экран.
var a,b:byte;
begin
write('Введите a:');
readln(a);
write('Введите b:');
readln(b);
asm
mov al,a
mov bl,b
mov ch,al
mov al,bl
mov bl,ch
mov a,al
mov b,bl
end;
writeln('A=',a);
writeln('B=',b);
end.
4. Дана переменная типа integer (word) – ее значение вводится с клавиатуры. Вывести байты старший и младший из которых она состоит.
var a:integer;
h,l:byte;
begin
write('Введите a:');
readln(a);
asm
mov ax,a
mov h,ah
mov l,al
end;
writeln(a,'=256*',h,'+',l);
end.
Как видите особо сложного ничего в присваивании переменным значения регистров и наоборот – нет. Тем не менее и здесь встречаются «подводные камни». К примеру решите такую задачу (речь идет о применении только команды mov).
“Даны две переменные действительного типа – поменять их значения местами”.
Такую задачу решить будет довольно сложно, особенно если учесть, что переменная действительного типа занимает 6 байт. А встроенный в PASCAL ассемблер работает с 2-х байтными переменными. По этому работу с действительными числами нам придется отложить до лучших времен.
ADD и SUB
1. Сложить два числа a,b
var a,b:integer;
sum:integer;
begin
writeln('Введите а и b:');
readln(a);
readln(b);
asm
mov ax,a
add ax,b
mov sum,ax
end;
writeln('Сумма:',sum);
end.
2. Сложить два числа.
Замечание:
Одно число имеет тип integer, второе число – тип byte.
var a:integer;
b:byte;
sum:integer;
begin
write('Введите а (значения от -32768 до 32768):');
readln(a);
write('Введите b (значения от 0 до 255):');
readln(b);
asm
mov ax,a
{складывать можно только AX и BX.
А BX состоит из BH+BL (старшая и младшая часть)
В младшую часть всегда помещаются числа размером byte.
А чтобы старшая чать не мешалась делаем ее равной нулю}
mov bl,b
mov bh,0
add ax,bx
mov sum,ax
end;
writeln('Сумма:',sum);
end.
3. Сложить три числа.
Замечание:
Числа имеют тип integer;
var a,b,c:integer;
sum:integer;
w:word;
begin
write('Введите а (значения от -32768 до 32768):');
readln(a);
write('Введите b (значения от -32768 до 32768):');
readln(b);
write('Введите c (значения от -32768 до 32768):');
readln(c);
asm
mov ax,a
mov bx,b
add ax,bx
add ax,c
mov w,ax
end;
writeln('Сумма:',w);
end.
4. К числу А (тип Integer) введенному с клавиатуры прибавить
число 100.
var a:integer;
begin
write('Введите а (значения от -32768 до 32768):');
readln(a);
asm
mov ax,a
add ax,100
mov a,ax
end;
writeln('A=',a);
end.
5. От числа введенного с клавиатуры отнять число 10 с помощью команды сложения.
var a:integer;
begin
write('Введите а (значения от -32768 до 32768):');
readln(a);
asm
mov ax,a
add ax,-10
mov a,ax
end;
writeln('A=',a);
end.
После этих задач у вас может возникнуть вопрос: что будет если сложить числа сумма которых будет больше 32768, ведь это максимально допустимое число? Для этого нам будет необходимо понять саму суть сложения двоичных чисел – ведь именно в двоичной системе складывает компьютер числа.
К примеру сложим 2+1=3
В двоичной системе 10+1=11
2+2=4
В двоичной 10+10=100
2+3=5
11+11=101
и т.д. При увеличении разряда числа слева добавляется одна единица (см. системы счисления)
Тип integer занимает 2 байта памяти т.е. 16 битов (нулей и или единиц). Причем 16 бит (старший) является служебным и равен 1 (установлен) когда число отрицательное или 0 (сброшен) когда положительное. Таким образом реально мы можем использовать только 15 битов – отсюда и число 32768 = 215.
И если число получилось при сложении больше 15 ячеек, что лишняя часть (все что следует за 15-ым битом – сбрасывается) т.е. реально из числа вычитается 215.
Работа со стеком.
Push и Pop
Как я уже писал в начале стек использует алгоритм filo. По этому очень подходит для решения задач рекурсии, различных проверок, работ со строками, ну и просто хранения данных.
Push – помещает значения в стек
Pop – вынимает значения из стека.
1. Поместить значение переменной а в стек. Затем взять из стека и вывести на печать.
uses crt;
var a:integer;
begin
clrscr;
a:=100;
asm
push a
mov a,200
pop a
end;
writeln('a=',a);
readln;
end.
2. Поместить в стек число 255. Затем верхнее значение стека поместить в переменную w.
uses crt;
var a:integer;
begin
clrscr;
a:=100;
writeln('a=',a);
asm
push 255
pop a
end;
writeln('a=',a);
readln;
end.
3. Поменять значения двух переменных местами используя стек.
uses crt;
var a,b:integer;
begin
clrscr;
a:=10;
b:=20;
asm
push a
push b
{это для наглядности см. ниже}
mov a,49
mov b,23
pop a
pop b
end;
writeln('a=',a);
writeln('b=',b);
readln;
end.
4. Даны переменные a,b,c,d,e,f,g,h,j,k,l. Поменять из значения таким образом, чтобы значение а было в l, b – в k и т.д.
uses crt;
var a,b,c,d,e,f,g,h,k,l:integer;
begin
clrscr;
a:=1;b:=2;c:=3;d:=4;e:=5;f:=6;g:=7;h:=8;k:=9;l:=10;
asm
push a;push b;push c;push d;push e
push f;push g;push h;push k;push l
pop l;pop k;pop h;pop g;pop f
pop e;pop d;pop c;pop b;pop a
end;
writeln(a);
writeln(b);
writeln(c);
writeln(d);
writeln(e);
writeln(f);
writeln(g);
writeln(h);
writeln(k);
writeln(l);
readln;
end.
5. Даны переменные a,b,c,d,e,f,g,h,j,k,l. Поместить их значения в стек. Затем поместить обратно и вывести на печать.
uses crt;
var a,b,c,d,e,f,g,h,k,l:integer;
begin
clrscr;
a:=1;b:=2;c:=3;d:=4;e:=5;f:=6;g:=7;h:=8;k:=9;l:=10;
asm
push a;push b;push c;push d;push e
push f;push g;push h;push k;push l
pop l;pop k;pop h;pop g;pop f
pop e;pop d;pop c;pop b;pop a
end;
writeln(a);
writeln(b);
writeln(c);
writeln(d);
writeln(e);
writeln(f);
writeln(g);
writeln(h);
writeln(k);
writeln(l);
readln;
end.
6. Поместить число типа byte в стек, затем взять из стека и вывести на печать.
uses crt;
var b:byte;
begin
clrscr;
b:=20;
asm
mov al,b
mov ah,0
push ax
pop bx
mov b,bl
end;
writeln('b=',b);
readln;
end.
Указания: постарайтесь найти принципиальные различия в данных программах. Обратите внимание на последовательность помещения значений в стек и последовательность из изъятия из стека.
Программа Значения a и b Стек
uses crt;
var a,b:integer;
begin
clrscr;
a:=10;
b:=20;
asm
push a
push b
mov a,49
mov b,23
pop a
pop b
end;
writeln('a=',a);
writeln('b=',b);
readln;
end.
Далее мы будет активно использовать в своей работе стек и по этому потренируйтесь с программами из примеров );. Попробуйте изменить или переставить значения переменных. Также для каждой задачи желательно составить такую таблицу, какая была показана выше.
Работа со стеком.
Push и Pop
Как я уже писал в начале стек использует алгоритм filo. По этому очень подходит для решения задач рекурсии, различных проверок, работ со строками, ну и просто хранения данных.
Push – помещает значения в стек
Pop – вынимает значения из стека.
1. Поместить значение переменной а в стек. Затем взять из стека и вывести на печать.
uses crt;
var a:integer;
begin
clrscr;
a:=100;
asm
push a
mov a,200
pop a
end;
writeln('a=',a);
readln;
end.
2. Поместить в стек число 255. Затем верхнее значение стека поместить в переменную w.
uses crt;
var a:integer;
begin
clrscr;
a:=100;
writeln('a=',a);
asm
push 255
pop a
end;
writeln('a=',a);
readln;
end.
3. Поменять значения двух переменных местами используя стек.
uses crt;
var a,b:integer;
begin
clrscr;
a:=10;
b:=20;
asm
push a
push b
{это для наглядности см. ниже}
mov a,49
mov b,23
pop a
pop b
end;
writeln('a=',a);
writeln('b=',b);
readln;
end.
4. Даны переменные a,b,c,d,e,f,g,h,j,k,l. Поменять из значения таким образом, чтобы значение а было в l, b – в k и т.д.
uses crt;
var a,b,c,d,e,f,g,h,k,l:integer;
begin
clrscr;
a:=1;b:=2;c:=3;d:=4;e:=5;f:=6;g:=7;h:=8;k:=9;l:=10;
asm
push a;push b;push c;push d;push e
push f;push g;push h;push k;push l
pop l;pop k;pop h;pop g;pop f
pop e;pop d;pop c;pop b;pop a
end;
writeln(a);
writeln(b);
writeln(c);
writeln(d);
writeln(e);
writeln(f);
writeln(g);
writeln(h);
writeln(k);
writeln(l);
readln;
end.
5. Даны переменные a,b,c,d,e,f,g,h,j,k,l. Поместить их значения в стек. Затем поместить обратно и вывести на печать.
uses crt;
var a,b,c,d,e,f,g,h,k,l:integer;
begin
clrscr;
a:=1;b:=2;c:=3;d:=4;e:=5;f:=6;g:=7;h:=8;k:=9;l:=10;
asm
push a;push b;push c;push d;push e
push f;push g;push h;push k;push l
pop l;pop k;pop h;pop g;pop f
pop e;pop d;pop c;pop b;pop a
end;
writeln(a);
writeln(b);
writeln(c);
writeln(d);
writeln(e);
writeln(f);
writeln(g);
writeln(h);
writeln(k);
writeln(l);
readln;
end.
6. Поместить число типа byte в стек, затем взять из стека и вывести на печать.
uses crt;
var b:byte;
begin
clrscr;
b:=20;
asm
mov al,b
mov ah,0
push ax
pop bx
mov b,bl
end;
writeln('b=',b);
readln;
end.
Указания: постарайтесь найти принципиальные различия в данных программах. Обратите внимание на последовательность помещения значений в стек и последовательность из изъятия из стека.
Рассмотрим работу стека на примере задачи №3.
Программа Значения a и b Стек
uses crt;
var a,b:integer;
begin
clrscr;
a:=10;
b:=20;
asm
push a
push b
mov a,49
mov b,23
pop a
pop b
end;
writeln('a=',a);
writeln('b=',b);
readln;
end.
Далее мы будет активно использовать в своей работе стек и по этому потренируйтесь с программами из примеров. Попробуйте изменить или переставить значения переменных. Также для каждой задачи желательно составить такую таблицу, какая была показана выше.
Команда сравнения и перехода. Ветвления в ассемблере.
CMP и JMP
Интересно много ли можно было сделать если бы в ассемблере не было команды сравнения? Наверное бы мы не сделали ничего и компьютер для нас бы был всего лишь большим калькулятором. Но, к счастью, такая команда е Ну и, конечно, надо отметить, что язык С++ не так известен, как язык PASCAL, и начинающему пользователю будет гораздо сложнее разобраться в ассемблерных вставках, если мы будем использовать еще и не знакомый язык программирования В ассемблере всего не белее десятка почтальонов Тем не менее и зде реально из числаinteger;
b вычитается 215сь встречаются «подводные камни»сть и называется она cmp.
Она позволяет сравнивать два операнда, которыми могут быть регистр и число, регистр и регистр, регистр и память. Если значения совпадают флаг нуля (см.флаги) устанавливается в единицу (или просто устанавливается). Если значения больше или меньше друг друга, то тут уже реагирует флаг переноса (переполнения). Кроме того флаг знака постоянно следит за тем положительное ли число сравнивается или отрицательное.
Если вам приходилось изучать язык FORTRAN или BASIC для ZX, то вы найдете много общего с языком ассемблера. Дело в том, что здесь нет реализации ELSE в команде ветвления.
В качестве инструкции перехода используется JMP. Причем существует множество разновидностей этой команды.
Проведем небольшую аналогию JMP и PASCAL, а затем напишем небольшую программу. Итак:
Ассемблер Аналогия в PASCAL
jmp <метка> goto <метка>
Jz <метка> if <выражение>=0 then goto <метка>
Jnz <метка> if <выражение> 0 then goto <метка>
jc <метка> if <выражение1> меньше <выражения2> goto <метка>
jnc <метка> if <выр.1> больше или равно <выр.2> goto <метка>
js <метка> if sgn(<выр.1>)=-1 then goto <метка>
jns <метка> if sgn(<выр.1>)=1 then goto <метка>
Особенно обратите внимание на выбеленные строки, поскольку именно при сравнении двух чисел на предмет больше одно другого или нет, возникает большинство ошибок.
Задача: Написать программу поиска максимального из двух чисел. Максимум поместить в переменную max.
label els,fn;
var a,b,max:integer;
begin
…..
{ввод двух чисел с клавиатуры или из файла}
……
if a>b then goto els;
max:=b;
goto fn;
els:
max:=a;
fn:
……..
end;
Довольно странная конструкция не правда ли? Учитывая, что в PASCALе можно записать несколько операторов в теле IF, и кроме того можно использовать ELSE – программа кажется очень странной.
Посмотрим, как будет выглядеть эта же самая программа с использованием ассемблерных вставок.
var a,b,max:integer;
begin
a:=1;
b:=3;
asm
mov ax,a
mov bx,b
cmp ax,bx
jc @els
mov max,ax
jmp @end
@els:
mov max,bx
@end:
end;
writeln(max);
end.
Как вы, наверное, уже успели заметить все внутренние метки в ассемблерных вставках должны начинаться с символа @. (спешл эй) и их не надо описывать в разделе LABEL самого начала программы.
Уже даже такую маленькую программу нелегко сделать без предварительной подготовке – возможно допустить ошибку. По этому перед тем как сесть и начать писать новую программу на ассемблере (тем более в начале вашей практики программирования на этом языке) желательно сперва набросать план программы на листке бумаги в виде блок схемы или простого алгоритма. Таким образом вы дважды напишите свою программу и шансов для появления ошибок будет значительно меньше.
Теперь рассмотрим задачи.
1. Найти минимум из 3 чисел.
var a,b,c,min:integer;
begin
a:=6;
b:=3;
c:=1;
asm
mov ax,a
mov bx,b
mov cx,c
cmp ax,bx
jnc @lp1
mov dx,ax
jmp @cont1
@lp1:
mov dx,bx
@cont1:
cmp dx,cx
jc @cont2
mov dx,cx
@cont2:
mov min,dx
end;
writeln(min);
end.
2. Дан x. Найти y если
var x,y:integer;
begin
x:=10;
asm
mov ax,x
cmp ax,10
{Дело в том, что условие x<=10
выполняться не будет по этому
необходима дополнительная
проверка - равно ли число
x десяти ли нет}
jz @cont1
jnc @cont2
@cont1:
add ax,100
@cont2:
add ax,2
mov y,ax
end;
writeln(y);
end.
3. Даны числа a и b. Найти их сумму по модулю. Результат поместить в переменную sum.
var a,b,sum:integer;
begin
a:=-10;
b:=20;
asm
mov ax,a
mov bx,b
cmp ax,0
jns @cont1
mov dx,65535
sub dx,ax
inc dx
mov ax,dx
@cont1:
cmp bx,0
jns @cont2
mov dx,65535
sub dx,bx
inc dx
mov bx,dx
@cont2:
add ax,bx
mov sum,ax
end;
writeln(sum);
end.
4. Дано число a. Найти s, если s=sgn(a).
var a,s:integer;
begin
a:=-2;
asm
mov ax,a
mov s,ax
cmp ax,0
jz @exit
js @cont
mov ax,1
mov s,ax
jmp @exit
@cont:
mov ax,-1
mov s,ax
@exit:
end;
writeln(s);
end.
Организация циклов.
Команды loop и jcxz.
Наверняка вы ужи и так догадались как можно организовать цикл с помощью оператора jnz. Если нет, то немножко расскажу как это делается.
1. Какому то регистру присваивается начальное знаечение. Например, регистру CX.
2. Устанавливается метка. Например @lp1
3. Выполняется тело цикла.
4. Уменьшается на единицу регистр CX.
5. Пока СХ не равен нулю выполняется переход на установленную метку. Jnz @lp1
Существуют еще два оператора которые могут пригодиться в программировании циклов. Рассмотрим следующий вариант цикла
1. Регистру CX присваивается начальное значение.
2. Устанавливается метка. Например @lp1
3. Выполняется тело цикла.
4. Loop @lp1
Что делает команда loop? Как видно из примера одна заменяет сразу два пункта из предыдущего примера (4-5). А именно автоматически уменьшает на единицу регистр СХ и пока он не равен нулю переходит на указанную метку. Из чего следует, что loop может использовать в качестве счетчика цикла только регистр CX.
Команда jcxz – выполняется аналогично команде jz, только еще осуществляется проверка на то равен ли регистр CX нулю.
Задачи.
1. Посчитать n факториал. При этом дополнительно (в PASCAL) проверить чтобы вводимое n не выходило за пределы допустимых значений.
uses crt;
var n:integer;
begin
clrscr;
write('Введите N:');
readln(n);
if n>9 then begin
writeln('Нельзя вводить N>9!');
halt;
end;
asm
mov cx,n
mov ax,1
@lp1:
mul cl
loop @lp1
mov n,ax
end;
writeln('Факториал равен:',n);
readln;
end.
2. Дано целое число а и натуральное (целое неотрицательное) число n. Вычислить а в степени n. Другими словами, необходимо составить программу, при исполнении которой значения переменных а и n не меняются, а значение некоторой другой переменной (например, b) становится равным а в степени n.
uses crt;
var a:integer;
n:byte;
begin
clrscr;
write('A=');
readln(a);
write('N=');
readln(n);
asm
mov bl,0
mov ax,1
mov bh,n
mov cx,a
@lp1:
inc bl
mul cx
cmp bl,bh
jnz @lp1
mov a,ax
end;
writeln('a^n=',a);
readln;
end.
3. Даны натуральные числа а, b. Вычислить произведение а*b,
используя в программе команду сложения.
uses crt;
var a,b:byte;
{мы не можем допустить чтобы умножались числа большие чем 255
так как 255*255=65535, что уже может вызвать переполнение}
res:integer;
{а вот резуьтат может оказаться большим числом}
begin
clrscr;
write('A=');
readln(a);
write('b=');
readln(b);
asm
mov bl,0
mov ax,0
mov cl,a
mov ch,0
mov dl,b
@lp1:
inc bl
add ax,cx
cmp bl,dl
jnz @lp1
mov res,ax
end;
writeln('a*b=',res);
readln;
end.
Существует довольно много книг по ассемблеру. Причем полки магазинов просто изобилуют ими. Но почему авторы этих книг не задумываются над вопросом «используется ли язык ассемблера сейчас в такой же степень, как хотя бы пару лет назад»? Нет, за последнее произошли не только перемены в области аппаратной части компьютерной индустрии, но и на рынке софта, а стало быть языков программирования. Еще каких ни будь 5 лет назад программирование под Windows казалось практически недоступным для рядового пользователя, подвластное только крупному профессионалу, программирующему на С++. Мало того объемы памяти были настолько малы по сегодняшним меркам, что написать большую программу на BASIC не представлялось возможным. Мало того требовалась скорость и потому программисты писали программы только в ассемблере или даже в машинных кодах. (я до сих пор помню наизусть коды нескольких операций машинного языка ZX). Конечно такие программы были весьма трудоемкими и на их разработку уходило от нескольких недель, до месяцев. Далее стали появляться все более и более мощные компьютеры. И соответственно языки программирования, например тот же самый BASIC становился все быстрее и быстрее. Таким образом в ассемблере как таковом перестают нуждаться и действительно зачем тратить огромное кол-во времени на оформление программы, процедуры ввода и вывода данных, когда их можно написать на том же самом BASICе за день, а саму процедуру обработки данных можно написать уже на ассемблере так как считаться будем она быстрее. Таким образом ассемблер как бы смешали с языками высокого уровня. Процедуры не слишком занимающие машинное время пишутся на языке высокого уровня, а если нужна скорость мы дописываем в программу ассемблерные команды. Программа получается как бы составленной из нескольких кусков – кусок текста языка высокого уровня, кусок ассемблера и т.д. Так появилось такое понятие, как «ассемблерная вставка». Наиболее простая реализация ассемблерных вставок (из мне известных) существует и применяется в языке PASCAL о нем то я и хочу поговорить.
Стоит задать вопрос: почему стоит преподавать ассемблер именно с ассемблерных вставок PASCAL, а не с TASM или другого компилятора «чистого» ассемблера?
Для этого я могу привести очень много причин:
1) Для студента (ученика) оболочка PASCAL является более привычным интерфейсом, чем какой либо редактор msdos.
2) простота компиляции. Достаточно нажать CTRL+F9 и программа запустится вместо того, чтобы каждый раз выходить из редактора и последовательно запускать программы link и tasm.
3) Сообщения об ошибках выдаются сразу же с указанием строки содержащей ошибку.
4) Не надо заботиться о выборе модели памяти и размещении данных, размере стека и т.д.
5) Нет необходимости тратить время на написание стандартных процедур ввода/вывода, не требующих быстродействия от процессора.
6) Удобная отладка. В Pascal встроен дебагер, ориентированный на работу с ассемблерными вставками.
7) Команды ассемблера, написанные вперемешку со стандартными командами PASCAL усваиваются лучше, что ассемблер сам по себе. Студентом проводиться аналогия с командами PASCALя.
8) Ассемблерные вставки, вставленные в программу легче объединить в модуль.
Особенности преподавания языка ассемблер
Курс ассемблера в отличии от курсов по другим языкам программирования требует особенного подхода так как сам по себе язык довольно сложный и не отличается структурированием в отличии от других языков. Кроме того ассемблер – язык в котором наиболее легко совершить ошибку в программе, ведь решение задачи порой очень даже не очевидно. По всему этому в изучении языка есть несколько особенностей, о которых мне бы хотелось рассказать.
1. Вероятнее всего начинать изучение ассемблера стоит проводя постоянные аналогии с тем, что более всего доступно для понимания обучаемому. Так, например, можно провести аналогию памяти с городом, сегменты с районами города, байты с номерами домов и т.д.
2. Также Вам потребуются множество наглядных плакатов, которые бы помогли запомнить курс. (см. приложение).
3. Все решаемые и задаваемые на дом задачи должны быть обязательно заранее решены и разобраны учителем. Задания, придуманные «на ходу» или взятые «из головы» совершенно не допустимы. Мало того формулировка задачи должна быть точной и понятной.
Сама по себе задача должна быть разделена на части, в которых как бы говорить, что надо сделать и как это надо сделать. Например:
Даны две переменные a, b (тип byte). С клавиатуры вводятся их значения.В ассемблерной вставке поменять значения переменных местами. Далее вывести значения обоих переменных на экран.
Условия задач, где можно допустить иную трактовку допускаться не должны. Например:
Поменять местами значения двух переменных.
Условие заранее является не совсем корректным так как не
понятно какого типа переменные менять местами, вводить ли их с
клавиатуры или они уже должны быть введены, а так же куда их
девать после того как значения местами заменены. Если бы
обучение было какому либо другому языку (PASCAL, BASIC,
C++ и т.д.) такая формулировка была бы верной, но ассемблер
требует четкости – лаконичность для него сама по себе не
свойственна.
4. Не надо стараться рассказать о всех командах и возможностях
ассемблера так как если человек не сможет разобраться с
основами, то «дебри» тем более никогда не освоит, а ассемблер
взаимосвязан.
5. Избегайте задач, которые можно решить только одним способом.
Должно быть несколько вариантов решения, а также сложных
задач (для этого см. №3 – задачи должны быть проверены).
Например, данную задачу скорей всего никто не решит если
она будет дана в начале (или даже в середине) курса ассемблера:
Даны два действительных числа, поменять их значения местами.
Кто знает как представляется действительное число в ассемблере?
Или, например, задача:
Сложить два (или больше) числа по модулю.
Как найти модуль?
Если такие задачи даются, то вначале необходимо объяснить алгоритм их решения и решать на доске.
6. Не стоит изучение ассемблера с помощью Turbo Debager или пакета tasm так как работа tasm лишь запутает начинающего изучение, а TD только уведет от языка так как данные и программа в нем представляется несколько по другому – преобразованной. Также отсутствует наглядность меток и переменных.
Задачи для самостоятельного решения
Звездочкой помечены задачи повышенного уровня сложности.
1. Дана переменная типа char. Присвоить ей значение 'a' с помощью ассемблера. Затем вывести на экран стандартным способом.
2. Даны две переменных cod (тип byte) и c (тип char).В переменной cod – находится ascii код символа. по коду символа в переменную c поместить сам символ. Вывести полученный символ на экран.
3. Дана переменная a (тип word). Поменять местами значения старших и младших байтов переменной. Вывести а на экран.
4. (**) Даны две переменные типа real – а и b. Присвоить а:=b и вывести на экран результат присвоения.
5. Присвоить регистру ES значение 1000.
6. Сложить два числа a, b, где и a, и b имеют тип byte – результат поместить в переменную c (тип integer)
7. Сложить три числа, два из которых типа byte, а третье типа integer.
8. Дана переменная а, содержащая какое то значение (значение вводится с клавиатуры). Вычисть из переменной такое число, чтобы после вычитания переменная а равнялась 0.
9. (*)Дана переменная а, содержащая какое то значение (значение вводится с клавиатуры). Вычисть из переменной такое число, чтобы после вычитания переменная а равнялась (- а).
10. (*) К числу А введенному с клавиатуры прибавить
число содержащееся в ячейки памяти 0000:[001сh].
11. Даны переменные а и b. С помощью команд сложения и
вычитания поменять содержимое переменных местами.
12. Поменять местами значения двух переменных с помощью стека, учитывая, что одна переменная имеет тип byte, а вторая integer.
13. Поместить в стек 5 чисел. Затем в переменную А поместить 2-ое число из стека.
14. Поместить напрямую в стек значение находящееся по адресу
0000:[001сh]. Затем помещенное число забрать из стека и вывести
на экран.
15. Написать программу поиска максимального из трех чисел. Максимум поместить в переменную max.
16. Найти минимум из 2 чисел.
17. Даны четыре числа a, b, c, d. Найти сумму тех из них, которые меньше (больше) нуля. Результат поместить в переменную sum.
18. Узнать сколько в двоичном представлении числа содержится
нулей.
19. (*) Узнать сколько в двоичном представлении числа содержится
двух рядом стоящих установленных битов.
20. Дан массив типа integer размерностью n. Найти сумму всех его элементов не превышающих заданного m, далее вывести ее на экран.
21. Дан массив типа integer размерностью n. Найти сумму всех его элементов по модулю, далее вывести ее на экран.
22. Найти в массиве максимальный элемент и его индекс. Вывести на печать.
23. (*) Дан массив типа integer. Найти максимальный элемент среди отрицательных элементов массива. Вывести его на печать.
24. Подсчитать сколько раз в массиве встречается нули (отрицательные элементы).
25. Подсчитать сколько раз в массиве встречается заданный элемент N. Вывести кол-во данных вхождений.
26. Дан массив типа integer. Упорядочить элементы массива по не убыванию. Вывести элементы отсортированного массива на экран.
27. Дан массив типа integer. Упорядочить элементы массива по не возрастаню. Вывести элементы отсортированного массива на экран.
28. Дан массив типа integer размером m. Сдвинуть все его элементы на n позиций влево. Первый элемент теряется.
29.(*) Дан массив типа integer размером m. Сдвинуть все его
элементы на n позиций влево. Причем при сдвиге –первый
элемент становиться последним, а остальные сдвигаются.
Вывести результат циклического сдвига на печать.
30.(***) Дан массив типа real размером m. Сдвинуть все его
элементы на n позиций влево. Первый элемент при сдвиге
теряется.
Для выполнения примеров на fpc компиляторе необходимо учитывать одну особенность, а именно, здесь применяется два вида синтаксиса ассемблера, в виндовсе по умолчанию включен интеловский синтаксис, а в линуксе АТТэшный. И чтобы ваши вставки работали везде, нужно указывать в директиве компилятора используемый синтаксис. АТТэшный синтаксис в основном отличается тем, что перед регистрами ставится значок "%", операнды меняются местами и в регистры приходится подавать вместо чисел переменные.
Например, пример из статьи
- Код: Выделить всё
var a:integer;
begin
asm
mov bx,10
mov a,bx
end;
writeln(a);
end.
преобразуется в такой
- Код: Выделить всё
{$ASMMODE Intel}
var a:integer;
begin
asm
mov bx,10
mov a,bx
end;
writeln(a);
end.
или такой
- Код: Выделить всё
{$ASMMODE ATT}
var a,b:integer;
begin
b:=10;
asm
mov b,%bx
mov %bx,a
end;
writeln(a);
end.
и нормально компилируется и выполняется!
uses crt;
var nд Написать программу поиска максимального из трех чисел