![]() |
1. Заголовок или название темы должно быть информативным
2. Все тексты программ должны помещаться в теги [CODE=asm] [/CODE]
3. Прежде чем задавать вопрос, см. "FAQ",если там не нашли ответа, воспользуйтесь ПОИСКОМ, возможно, такую задачу уже решали!
4. Не предлагайте свои решения на других языках, кроме Ассемблера. Исключение только с согласия модератора.
5. НЕ используйте форум для личного общения! Все, что не относиться к обсуждению темы - на PM!
6. Проверяйте программы перед тем, как выложить их на форум!!
![]() |
BlackShadow |
![]() ![]()
Сообщение
#1
|
Гость ![]() |
От нечего делать опишу основные моменты при программировании на встроенном ассемблере в Паскале.
Есть 2 способа задействовать эту замечательную возможность:
Ну, на самом деле применений целое море, о чём можно убедиться полистав этот раздел форума. Рассмотрим вопрос адресации в реальном режиме (именно он используется "by def" при компиляции в BP). Адрес состоит из двух частей: сегментной части и смещения. Обе части являются 16-ти разрядными двоичными числами или, что на практике и применяется, 4-х разрядными шестнадцатиричными. Рассмотрим пример и на нём разберёмся, что и какая часть значит: Пусть сегментная часть (далее Seg) = $ABCD, а смещение (далее Ofs) = $1234. Это означает, что эта пара Seg:Ofs хранит следующий адрес Seg * $10 + Ofs = $ABCD * $10 + $1234 = $ABCD0 + $1234 = $ACF04. Как легко заметить, пользуясь таким способом адресации мы можем указать адрес любой ячейки памяти в пределах первого мегабайта ($00000..$FFFFF) и даже чуть-чуть больше, но это не имеет значения, т.к. процессор в реальном режиме даёт доступ только к первому МБ. Возникает естественный вопрос: а зачем нужен этот геморрой, и почему нельзя просто указывать полный адрес? А вот нельзя. А потому, что процессор при работе с памятью опирается на информацию, которая хранится в его регистрах, а т.к. мы используем 16-битный вариант команд, то, соответственно, в 1 регистр более 16 бит (4 16-ричные цифры) не впихнуть. Поэтому и приходится использовать 2 регистра: сегментный и какой-нибудь, который можно использовать для адресации. Рассмотрим предназначение регистров процессора:
Теперь давайте рассмотри примитивный набор команд. mov dst, src копирует значение src в dst. Есть один важный момент: командой mov нельзя скопировать значение одной переменной в другую за один приём. Примеры: (Показать/Скрыть)
inc p увеличивает на 1 значение операнда. После компиляции эта команда занимает меньше места чем команда прибавления единицы. Примеры: (Показать/Скрыть)
dec p соответственно уменьшает операнд на 1. add dst, src прибавляет к src значение dst. Результат сохраняется в dst, так что просто число там написать нельзя. Примеры: (Показать/Скрыть)
sub dst, src вычитает из dst значение src. mul n умножает значение регистра AL (AX) на n. Если n размером в 1 байт, то происходит следующее: AX = AL * n, если же слово, то старшие 16 бит произведения сохраняются в DX, а младшие в AX, т. е., умножив AX=$1010 на $100 получим в DX $0010 и в AX $1000. Примеры: (Показать/Скрыть)
div n делит значение в AX (DX:AX, как в команде mul) на n. При этом остаток сохраняется в AH (DX), а целая часть от деления в AL (AX). Например: (Показать/Скрыть)
cmp a, b - сравнивает значения a и b и устанавливает флаги процессора в соответствии с результатом сравнения. Например: (Показать/Скрыть)
jmp L - команда безусловного перехода на метку L. То что в BP называется GoTo. Например: (Показать/Скрыть)
j<cc> L - серия команд условного перехода. Тут <cc> определяет условия перехода:
Например: (Показать/Скрыть)
Рассмотрим ещё команду loop L она сравнивает CX с 0 и, если он отличен, то уменьшает его на 1 и делает переход на указанную метку. Пример: (Показать/Скрыть)
Теперь для закрепления сказанного рассмотрим реализацию вычисления факториала:
Function Factorial(n: Integer): Integer; Assembler;
Asm
mov CX, [n] {Проверим, может n<0?}
cmp CX, 0 {Сравним с 0}
jl @@1 {Если меньше, то считать не будем}
mov AX, 1 {Начальное произведение}
@@2: {В CX мы уже загрузили кол-вл итераций, так что к циклу готовы}
mul CX {Домножим текущее произведение на значение счётчика}
loop @@2 {И продолжим цикл}
jmp @@3 {Произведение вычислено - можно выходить из функции}
@@1: {А, если попросили вычислить факториал отрицательного числа}
mov AX, 0 {То вернём 0}
End
Var n: Integer;
Begin
ReadLn(n);
WriteLn(Factorial(n))
End.
Стоит объяснить ещё и то, как возвращаются значения функций. Это всё зависит от типа результата: Byte, Char - через AL Word, Integer - через AX LongInt - старшая часть в DX, а младшие 16 бит в AX. Pointer - сегментная часть в DX, смещение - AX. Остальные типы возвращаются более извращённым способом... Так же отмечу, что убрав проверку на <0 можно переписать эту функцию так: Function Factorial(n: Integer): Integer; Assembler;
Asm
mov CX, [n]
mov AX, 1
@@1:
mul CX
loop @@1
End
Правда проблема переполнения остаётся, но зато покажите мне компилятор, который стандартное Function Factorial(n: Integer): Integer;
Var
i, Res: Integer;
Begin
Res:=1;
For i:=2 To n Do
Res:=Res*n
Factorial:=Res
End;
скомпилирует вот так вот красиво... |
![]() ![]() |
BlackShadow |
![]()
Сообщение
#2
|
Гость ![]() |
Теория это хорошо, но перейдём к
Практика использования Довольно редко приходится заменять то, что уже написано разработчиками из Borland'а. Всё-таки там тоже не дураки сидят. Но иногда приходится написать что-нибудь своё... А потом прикинуть и понять, что на Assembler'е это куда симпатичнее выходит. Есть такая команда INT <n>, которая вызывает указанное прерывание. Важно отметить то, что <n> это число и только число. Прерывания - это набор каких-либо функций, которые предоставляются BIOS'ом, DOS'ом и некоторыми дровами. Откуда процессор знает где храниться обработчик какого-нибудь прерывания? Объясняю. Есть такая таблица прерываний. Она начинается с самого начала оперативки (с адреса 00000h) и занимает 1024 байта. Там для каждого прерывания храниться вектор (адрес обработчика). Таким образом команду INT n
можно заменить чем-то вроде
XOR AX, AX
MOV ES, AX
MOV DI, 4*n
CALL FAR [ES:DI]
Вот только сбиваются ES и AX, чего как правило делать нельзя. Что мы можем получить от прерываний Мы можем получить доступ к функциям BIOS'а. Они доступны через прерывания
Ещё вектора прерываний указывают на некоторые таблицы BIOS'а. Например таблицы параметров FDD, HDD, таблицы символов... И т.д. Есть ещё аппаратные прерывания. Их не вызывают, они сами приходят ![]() Самая крупная и, наверно, многофункциональная, группа прерываний, это прерывания установленные операционной системой. Они располагаются в диапазоне $20-$2F. И вот что они позволяют:
Осталась последняя группа прерываний - те, которые устанавливаются by ПО. Например INT 33h предоставляет возможность работы с мышью, если запущен *Mouse.Com. Точнее это он перехватывает это прерывание и предоставляет вам какие-то возможности. Ну вот, хотел перейти к практике, а уже столько теории впаял... Давайте тогда хотя бы две функции напишем, которые практически ОЧЕНЬ нужны, а создав модуль с ними, мы получаем возможность не подключать CRT только ради них (ну не нравится мне CRT - глючит он, а патчи искать как правило влом):
Function KeyPressed: Boolean; Assembler;
Asm
MOV AH, 1 { Номер функции прерывания как правило передаётся
через AH, по крайней мере в DOS'е и BIOS'е }
INT 16h { Работа с клавой }
{ Если в буфере клавы чего-то есть, то это чего-то возвращается в AX,
но из буфера не стирается, а если нету, то устанавливается флаг ZF }
JNZ @@1
XOR AL, AL {Не нажата. Надо вернуть False, т. е. 0 в AL}
JMP @@2
@@1:
MOV AL, 1 {Нажата. Вернём True - 1 в AL}
@@2:
End
Function ReadKey:Word;Assembler;
{
Да вот такой вот ReadKey - не Char, а Word.
Отличие в том, что для "обычной" клавиши ReadKey=Word(Key),
а для клавиш типа F1, стрелок и т. п. ReadKey=$??00.
Мне так удобнее...
}
Asm
XOR AH,AH {Функция №0. Если в буфере клавы что-то есть, то
читает оттуда, иначе ждёт нажатия}
INT 16h
OR AL,AL {Если в AL попал 0, то в AH - дополнительный код,
т. е. результат уже подготовлен}
JZ @@1
XOR AH,AH {Иначе обнулим AH на всякий случай, чтобы подогнать
значение из AL до слова}
@@1:
End
Как видите, пара строк, а CRT из-за этих двух функций подключать уже не надо. А ведь частенько при экспериментах с графикой нужно что-то типа ReadKey, чтоб паузу сделать и посмотреть, что получается. |
![]() ![]() |
![]() |
Текстовая версия | 27.07.2025 2:13 |