Главная Учебники - Разные Лекции (разные) - часть 32
Раздел 1:
Регистры и
параметры
1.1 Использование
регистров
В создаваемых
ассемблерных
программах
можно использовать
все регистры
процессора.
Но чтобы предотвратить
путаницу с
функциями С
и С++, необходимо
восстанавливать
bp, cs, sp и ss, которые
они имели до
запуска созданной
подпрограммы.
Тогда можно
быть совершенно
уверенным, что
обращение к
другим функциям
не изменит эти
регистры. Также
нельзя забывать,
что С использует
регистры si и
di для регистровых
переменных,
поэтому при
использовании
встроенного
ассемблера
замедляется
общая работа
программы.
К регистрам
ax, bx, cx, dx и es можно
обращаться
свободно и не
нужно резервировать
их значения
до окончания
подпрограммы.
Эта свобода
касается и
других функций,
поэтому надо
помнить, что
эти регистры
изменяться,
если вызываются
функции С и С++
из ассемблерных
подпрограмм.
1.2 Ассемблерные
операторы
Inline
Ассемблерные
операторы
inline начинаются
словом asm, за
которым следует
инструкция
и ее операнды.
Например, чтобы
синхронизировать
программу с
внешним сигналом
прерывания,
можно написать:
/* ожидание
прерывания*/
asm sti
asm hlt
printf(“Прерывание
получено\n”)
Когда ранние
версии Turbo C компилируют
программу со
встроенными
командами asm,
компилятор
сперва создает
ассемблерный
текст для всей
программы,
вставляя в
текст наши
ассемблерные
инструкции
вместе с откомпилированным
кодом для остальных
операторов
С. Затем компилятор
вызывает Turbo
Assembler и Linker (компоновщик),
чтобы провести
ассемблирование
и подключить
программу к
конечному файлу
кода. Более
поздние версии
Turbo и Borland C++ могут
компилировать
операторы asm
без вызова
TASM. Полный синтаксис
asm:
asm[метка]
мнемоника/директива
операнды [;] [/*С
комментарий*/]
Точки с запятыми
в конце строк
asm и комментарии
С, расположенные
между /*и*/ удаляются
из текста перед
ассемблированием,
поэтому их
можно опускать
в тексте программы.
1.3 Размещение
данных и операторов
в тексте программы
Каждая строка
текста программы
С и С++ находится
либо внутри,
либо снаружи
функции, и операторы
asm могут вставляться
как в одном,
так и в другом
месте. Конкретное
положение
оператора asm
влияет на то,
куда ассемблируется
код или директива.
Если оператор
asm появляется
снаружи функции,
то он ассемблируется
в сегмент данных
программы, если
внутри функции
- в кодовый сегмент.
Обычно, чтобы
создать переменные,
операторы asm
вставляются
снаружи функций;
для создания
кода их следует
вставлять
внутрь функций.
Например:
asm count db ?
int main()
{
asm shl [count], 1/*умножение
count на 4*/
asm shl [count], 1
return 0;
}
Переменная
count объявляется
в сегменте
данных программы
(относительно
ds). Операторы
внутри функции
main умножают count
на 4, используя
вместо mul быстрые
инструкции
сдвига shl. Если
объявлять
переменные
внутри функции,
данные ассемблируются
в кодовый сегмент,
требуя особого
обхождения:
int main()
{
asm jmp OverThere
asm count db ?
OverThere:
asm shl [count], 1 /* умножение
count на 4*/
asm shl [count], 1
return 0;
}
Поскольку
теперь переменная
count находится
в кодовом сегменте,
требуется
инструкция
jmp, чтобы избежать
случайного
восприятия
значения count в
качестве машинного
кода и его
исполнения.
Раздел 2:
Особенности
данных
2.1 Разделение
данных
Inline операторы
asm имеют свободный
доступ к переменным
и структурам
С и C++ - одно из
самых привлекательных
преимуществ
метода inline по
сравнению с
подходом посредством
внешних модулей.
Самое интересное
в типах данных
ассемблера
и С++, что dq может
использоваться
для создания
инициализированных
переменных
двойной длины
с плавающей
точкой в языке
ассемблера,
но в Turbo Assembler отсутствуют
директивы для
непосредственного
создания переменных
с плавающей
точкой.
В операторах
asm можно ссылаться
на переменные
типов С++. Например:
unsigned char initial
initial = 'T'
asm mov dl, [initial] /*Загрузка
символа в dl*/
asm mov ah, 2 /* Пересылка
символа в ДОС
*/
asm int 21h /* Стандартная
выходная функция
*/
Беззнаковая
символьная
переменная
initial загружается
оператором
asm в dl. Так как и
dl, и беззнаковые
символьные
данные имеют
длину один
байт, нет необходимости
в ссылке использовать
определитель
Byte, хотя его применение
и не будет ошибкой:
asm mov dl, [Byte ptr initial]
2.2 Объявление
ассемблерных
данных
Можно объявить
переменные
для использования
только ассемблерными
операторами.
Например, чтобы
создать 16-битовое
слово с именем
TwoBytes и загрузить
значение переменной
в сх, можно написать:
asm TwoBytes db 1, 2
int main()
{
asm mov cx, [Word ptr TwoBytes]
return 0
}
Переменная
TwoBytes объявляется
в сегменте
данных программы
(снаружи функции),
с использованием
директивы db,
чтобы хранить
в памяти 2 байта
(1 и 2). Оператор
ассемблера
затем загружает
TwoBytes в сх. Определитель
Wordptr необходим
для ссылки на
TwoBytes как на 16-битовое
слово.
Поскольку
TwoBytes объявляется
в операторе
asm, на эту переменную
нельзя ссылаться
в тексте программы
С или C++. Поэтому,
если только
не требуются
отдельные
переменные
для ассемблерных
инструкций,
следует объявлять
их обычным
образом и ссылаться
на них из ассемблерного
модуля.
2.3 Разделение
кода
Ассемблерные
операторы
inline могут вызывать
функции С и
C++, а операторы
С и C++ обращаться
к функциям,
написанным
полностью на
ассемблере.
Вся эта взаимосвязь
будет хорошо
рассмотрена
в данном курсовом
проекте.
Раздел 3: Вызов
ассемблерных
функций из С
3.1 Символы
подчеркивания
Как показывает
практика, все
символы PUBLIC и
EXTERN должны начинаться
символами
подчеркивания.
Это необходимо
делать только
в ассемблерном
модуле (но не
в исходной про
грамме С или
C++), поскольку
компилятор
добавляет
подчеркивание
ко всем глобальным
символам, если
только не
используется
опция -u для
компиляции
программ. (Не
стоит применять
эту опцию, если
только не надо
также перекомпилировать
всю используемую
при выполнении
С библиотеку,
в которой
предполагается,
что все глобальные
символы начинаются
символом
подчеркивания.)
Если во время
компоновки
выходит сообщение
об ошибке "undefined
symbol" (неопределенный
символ), то причиной
может оказаться
отсутствие
подчеркивания
в ассемблерном
модуле.
3.2 Использование
дальних данных
Если объявляются
переменные
в дальнем сегменте
данных после
ключевого слова
FARDATA, то необходимо
подготовить
сегментный
регистр к размещению
переменных
в памяти. Прежде
всего, вслед
за директивой
FARDATA необходимо
объявить данные:
FARDATA
_OuterLimits dw ?
Затем, в кодовом
сегменте, следует
перед использованием
переменной
подготовить
сегментный
регистр. Одним
из возможных
подходов является
использование
оператора SEG
для загрузки
адреса дальнего
сегмента данных:
mov ax, SEG_OuterLimits;Адресует
дальний сегмент
;данных
mov es, ах
;посредством
es
mov [es:_OuterLimits], dx ;Резервируется
dx для пере ;менной
Можно также
использовать
заранее определенный
символ @fardata:
mov ах, @fardata
mov es, ах
mov [es:_OuterLimits], dx
3.3 Вызов ассемблерных
функций из С
Чаще всего
asm код оформляют
в виде отдельных
asm функций, которые
потом соединяют
на этапе загрузки.
Ассемблерный
код должен
поддерживать
стиль языка
С для вызова
функций, которые
включают передачу
параметров,
возврат значений
и правила сохранения
регистров,
которые требуются
для С++ функций.
Компилятор
и загрузчик
должны работать
совместно для
обеспечения
вызовов между
модулями. Процесс
называется
исключением
или ломкой
имен. Предусматривает
наличие информации
о типе аргумента.
Ломка имени
изменяет имя
функции, показывая,
что за аргумент
принимает
функция. Когда
мы конструируем
на языке С++, то
ломка имен
происходит
автоматически.
Когда же
пишется модуль
на asm, который
должен быть
подсоединен
к модулю на
языке С++, необходимо
позаботиться
о том, чтобы
asm модуль содержал
ломку имен
(знак подчеркивания).
Это можно сделать
путем написания
фиктивной
функции на С++
и компилировать
его в asm код. И
ассемблерный
файл, который
будет сгенерирован
компилятором
С++, будет содержать
ломку имен,
которые мы
можем потом
использовать
при написании
asm функции:
@ имя класса
@ исходное имя
функции $g описание
типов
, где
@ - связующий
символ
$ - конец исходного
имени функции
g - начало описания
типов параметров
Пример:
void test() {}
void test(int...) {}
void test(int, int) {}
void test(float, double) {}
Что будет
в ассемблере?
1. @ test $ gv proc near
push bp
mov bp, sp
pop bp
ret
@ test $ gv endp
2. @ test $ gi proc near
.............
@ test $ gi endp
3. @ test $ gii proc near
............
@ test $ gii endp
4. @ test $ gfd proc near
............
@ test $ gfd endp
BC++ разрешает
использование
неискаженных
имен asm функций,
определяя
стандартные
имена С функций
в С программах.
Для этого в
программе
определяется
внешний С блок:
extern “C”{
int add(int *a, int b);
}
________________
public _add
_add proc ...и т.д.
Определение
или декларирование
asm функции во
внешнем С блоке
избавляет
программиста
от необходимости
определения
действительного
имени ассемблерной
функции и повышает
наглядность.
С++ передает
параметры
функции через
стек. Перед тем
как вызвать
функцию С++ помещает
параметры в
стек, начиная
с последнего.
3.4 Ассемблирование
и компоновка
внешних модулей
Существует
несколько
методов ассемблирования,
компиляции
и компоновки
отдельных
модулей (и
аналогичных
многофайловых
программ) для
создания конечной
.ЕХЕ программы.
Проще всего
предоставить
проделать все
это Turbo С:
tcc cfillstr cfill.asm/*первый
модуль - cfillstr.c*/
Если используется
Borland C++, надо ввести
следующую
команду (заменить
bсс на tсс для
Turbo C++):
bcc cfillstr.с cfill.asm
В любом случае
команда сперва
компилирует
CFILLSTR.C в CFILLSTR.OBJ. Затем,
распознав
расширение
имени файла
.ASM как имя ассемблерного
модуля, компилятор
вызывает Turbo
Assembler, чтобы ассемблировать
CFILL.ASM в CFILL.OBJ. И наконец,
компилятор
вызывает Turbo Linker
для объединения
модулей с объектными
кодами в CFILLSTR.EXE. Если
компиляции
и ассемблированию
подлежит небольшое
количество
модулей, этот
одношаговый
метод наиболее
прост для
использования.
Если у имеется
большое количество
модулей, можно
сэкономить
время, осуществляя
ассемблирование
и компоновку
раздельно.
Первым шагом
следует ассемблировать
все .ASM файлы.
Поскольку
пример flllsrting обладает
только одним
таким файлом,
все можно проделать
с помощью
единственной
команды
tasm /ml cfill.asm
Опция /ml включает
различение
строчных и
прописных
символов, чтобы
к примеру слова
UpAndDown и upanddown рассматривались
как различные
- как это принято
в программах
С и C++. (Turbo Assembler обычно
не обращает
внимания на
то, в каком регистре
набраны символы,
поэтому опция
/ml необходима
во избежание
ошибок при
компоновке.)
После ассемблирования
всех внешних
модулей откомпилировать
основную программу.
Опять же, поскольку
в этом примере
имеется только
один файл .С,
для этого необходима
только одна
команда
tcc -с cfillstr.c
Если используется
Borland C++, надо применить
следующую
команду (заменить
bсс на tсс для
Turbo C++):
bсс -с cfillstr.c
Опция -с означает
"только компилировать",
вызывая создание
CFILLSTR.OBJ, но не компоновку
программы в
законченный
кодовый файл.
Для включения
всех модулей
необходимо
выполнить этот
шаг самим, вызывая
Turbo Linker для объединения
файлов с объектным
кодом с соответствующими
библиотечными
подпрограммами
для создания
CFILLSTR.EXE. Существует
два метода
компоновки.
Сначала рассмотрим
более сложный
метод:
t1ink с:\tc\lib\c0s cfillstr cfill, cfillstr,,
с:\tc\lib\cs
При использовании
Borland C++4 можно применить
следующую
команду:
tlink с:\bc4\lib\c0s cfillstr cfill, cfillstr,,
с:\bc4\lib\cs
Первый член
после tlink специфицирует
файл объектного
кода в директории
\LIB для соответствующей
модели памяти,
в данном случае
- COS.OBJ. Второй и третий
члены перечисляют
подлежащие
компоновке
файлы с объектным
кодом .OBJ - они
могут быть
перечислены
в любом порядке.
Запятая отделяет
список файлов
.OBJ от имени, которое
должно использоваться
для конечного
файла, в данном
случае - CFILLSTR-EXE. Две
запятые, следующие
за этим, показывают
место необязательного
файла карты
(*.map), не создающегося
в данном примере.
И наконец,
специфицируется
рабочая библиотека
- также в каталоге
\LIB.
Имена файла
с объектным
кодом COS и библиотечным
файлом CS должны
соответствовать
модели памяти,
используемой
программой.
Упрощенный
(но не очень
быстрый) метод
компоновки
отдельных
модулей состоит
в использовании
компилятора
в качестве
"первой части"
Turbo Linker. Другими
словами, вставляя
различные
команды компиляции,
можно пропустить
компиляцию
и перейти прямо
|