![]() |
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 и делает переход на указанную метку. Пример: (Показать/Скрыть)
Теперь для закрепления сказанного рассмотрим реализацию вычисления факториала:
Стоит объяснить ещё и то, как возвращаются значения функций. Это всё зависит от типа результата: Byte, Char - через AL Word, Integer - через AX LongInt - старшая часть в DX, а младшие 16 бит в AX. Pointer - сегментная часть в DX, смещение - AX. Остальные типы возвращаются более извращённым способом... Так же отмечу, что убрав проверку на <0 можно переписать эту функцию так: Function Factorial(n: Integer): Integer; Assembler; Правда проблема переполнения остаётся, но зато покажите мне компилятор, который стандартное Function Factorial(n: Integer): Integer; скомпилирует вот так вот красиво... |
![]() ![]() |
BlackShadow |
![]()
Сообщение
#2
|
Гость ![]() |
Голова болит, работа достала... Ну как тут не вспомнить про Assembler?
![]() Строковые команды Из названия можно было бы подумать, что в assembler'е тоже есть что-то "стандартное" типа Length или Copy. А вот и нет. Эти команды просто довольно часто используются для работы со строками или просто с цепочками данных. Но для начала объясню действие ещё 2 команд: LDS/LES <Reg>,<Src>, которые загружают из 4-ёх байтового указателя значения в регистр DS/ES соответственно (сегментную часть указателя) и в указанный регистр вторую часть указателя - смещение. Эти команды довольно удобны, т.к. все указатели Pascal'я имеют именно такую структуру. Вернёмся к строковым командам. Общий принцип таков:
Теперь рассмотрим сами команды. LODSB/LODSW загружает в AL или AX соответственно (последняя буква в названии команд этого типа и обозначает размер операнда: W - слово, B - байт) значение из DS:SI. Для наглядной иллюстрации напишем функцию StrLen Function StrLen(s:PChar):Integer;Assembler; Команда STOSB/STOSW сохраняет значение AL/AX по адресу ES:DI. Пример - функция, обнуляющая строку: Procedure StrNull(s:PChar);Assembler; MOBSB/MOVSW копируют байт/слово из DS:SI в ES:DI. Для примера возьмём процедуру копирования строки: Procedure StrCopy(Dst:PChar;Src:PChar);Assembler; SCASB/CMPSW Сравнивает значение источника с AL/AX. Для примера перепишем StrLen: Function StrLen(s:PChar):Integer;Assembler; CMPSB/CMPSW сравнивают два байта/слова из DS:SI и ES:DI и в соответствии с этим устанавливают флаги. Как пример можно привести сравнение строк, но мне, если честно, откровенно влом сейчас заниматься такими извратами. WARNING!!! Все примеры приведённые тут являются чисто демонстрационными! Никакого практического применения они иметь не должны, т. к. далеко не оптимальны. А вот намного всё упрощают и улучшают префиксы повторений REPxx. Возможные варианты: REP/REPE/REPNE (ну или REP/REPZ/REPNZ, если так кому больше нравится). Команда с таким префиксом выполняется CX раз, если тому не помешает условие. Следующий пример уже можно использовать в жизни: Function StrLen(s:PChar):Integer;Assembler; Можно оптимизировать эту функцию и далее, но при помощи команд, которые я ещё не описывал. Ну и напоследок приведу пример, в котором команда из этого набора используется не для работы со строками: Procedure VsyoNeboVPopugayah;Assembler; Ну вот вроде и всё, что я хотел сказать на эту тему... |
![]() ![]() |
![]() |
Текстовая версия | 17.06.2025 23:54 |