Раздел 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 в
качестве машинного
кода и его
исполнения.
7
Раздел 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++ обращаться
к функциям,
написанным
полностью на
ассемблере.
Вся эта взаимосвязь
будет хорошо
рассмотрена
в данном курсовом
проекте.
8
Раздел 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 с:tclibc0s cfillstr cfill, cfillstr,,
с:tclibcs
При использовании
Borland C++4 можно применить
следующую
команду:
tlink с:bc4libc0s cfillstr cfill, cfillstr,,
с:bc4libcs
Первый член
после tlink специфицирует
файл объектного
кода в директории
LIB для соответствующей
модели памяти,
в данном случае
- COS.OBJ. Второй и третий
члены перечисляют
подлежащие
компоновке
файлы с объектным
кодом .OBJ - они
могут быть
перечислены
в любом порядке.
Запятая отделяет
список файлов
.OBJ от имени, которое
должно использоваться
для конечного
файла, в данном
случае - CFILLSTR-EXE. Две
запятые, следующие
за этим, показывают
место необязательного
файла карты
(*.map), не создающегося
в данном примере.
И наконец,
специфицируется
рабочая библиотека
- также в каталоге
LIB.
Имена файла
с объектным
кодом COS и библиотечным
файлом CS должны
соответствовать
модели памяти,
используемой
программой.
Упрощенный
(но не очень
быстрый) метод
компоновки
отдельных
модулей состоит
в использовании
компилятора
в качестве
"первой части"
Turbo Linker. Другими
словами, вставляя
различные
команды компиляции,
можно пропустить
компиляцию
и перейти прямо
к компоновке.
Это дает возможность
избежать
необходимости
специфицировать
имена рабочих
библиотечных
файлов и, следовательно,
упрощает команду
компоновки.
Например, для
ассемблирования,
компиляции
и компоновки
CFILLSTR этим методом
требуются три
команды:
tasm /ml cfill.asm
tee -с cfillstr.c
tee -ms cfillstr.obj cfill.obj
Для Borland C++ надо
ввести следующие
команды (заменить
bсс на tсс для
Turbo C++):
tasm /ml cfill.asm
bee -с cfillstr.с
bee -ms cfillstr.obj cfill.obj
Первые две
команды - те
же, что были
описаны выше.
Третья вызывает
компилятор
во второй раз,
используя опцию
-ms для определения
модели памяти,
в данном случае
small (малая). Вслед
за опцией модели
памяти идут
файлы с объектным
кодом, подлежащие
компоновке.
Хотя приходится
включать расширение
имен файлов
.OBJ в перечисление
каждого файла,
этот не очень
длинный метод
компоновки
упрощает большую
часть черновой
работы по
непосредственному
запуску Turbo Linker. Для
того, чтобы
узнать, какую
опцию надо
писать при TCC,
можно воспользоваться
следующей
таблицей.
Таблица
3.1: Имена файлов
рабочей библиотеки
Модель памяти | Объектный файл | Библиотечный файл | Опция TCC | ||||||
Tiny | c0t.obj | cs.lib | -mt | ||||||
Small | c0s.obj | cs.lib | -ms | ||||||
Medium | c0m.obj | cm.lib | -mm | ||||||
Compact | c0c.obj | cl.lib | -mc | ||||||
Large | c0l.obj | cl.lib | -ml | ||||||
Huge | c0h.obj | ch.lib | -mh
Обсуждение:
Название реферата: Взаимосвязь языков C и ассемблера
|