Интерфейс турбо ассемблера и турбо си

Скачать доклад: Интерфейс турбо ассемблера и турбо си

Для смешанного программирования на языке высокого уровня и Ассемблере прекрасно подходит Турбо Си. Для объединения кода Ассемблера и Си в нем предусмотрен не один, а целых два механизма.

Средство встроенного Ассемблера в Турбо Си обеспечивает быстрый и удобный способ для включения кода Ассемблера непосредственно в функцию Си. Для тех, кто предпочитает при программировании на Ассемблере использовать отдельные модули, целиком написанные на этом языке, такие модули можно ассемблировать отдельно, а затем компоновать с программами Турбо Си.

Встроенный Ассемблер - это не что иное, как возможность вставлять практически любой код Ассемблера в программы на Си.

Каждый раз, когда Турбо Си обнаруживает ключевое слово asm, указывающее, что это строка Ассемблера, он помещает данную строку Ассемблера непосредственно в скомпилированный код с одним изменением: ссылки на переменные Си преобразуются в соответствующий эквивалент на Ассемблере. Предусмотрена возможность объединения инструкций встроенного ассемблера в блоки с испотльзованием конструкции asm {...} .

Турбо Си компилирует свой код таким образом, чтобы избежать многих потенциально опасных взаимодействий со встроенным Ассемблером. Тем не менее, неправильно функционирующий встроенный код Ассемблера определенно может привести к серьезным ошибкам. Однако ошибки во встроенном коде Ассемблера гораздо менее вероятны, чем в программе, целиком написанной на Ассемблере, поскольку Турбо Си берет на себя множество мелочей, таких, как вход в функции и выход из них, передачу параметров и выделение памяти для переменных.

Как работает встроенный Ассемблер

Обычно Турбо Си компилирует каждый файл исходного кода на языке Си в объектный файл, а затем вызывает утилиту TLINK для компоновки объектных файлов в выполняемую программу.

Однако при использовании встроенного Ассемблера Турбо Си добавляет в цикл компиляции и компоновки дополнительный шаг.

При обработке Турбо Си каждого модуля, где содержится встроенный код Ассемблера, сначала весь модуль компилируется в исходный файл на языке Ассемблера, а затем для трансляции полученного кода Ассемблера а объектный код вызывается Турбо Ассемблер. После этого для компоновки объектных файлов вызывается утилита TLINK.

Встроенный код Ассемблера просто передается Турбо Си в файл на языке Ассемблера. Турбо Си не нужно ничего знать об ассемблировании встроенного кода, вместо этого Турбо Си компилирует исходный код языка Си на уровень Ассемблера, а затем позволяет Турбо Ассемблеру выполнить трансляцию.

Обычно Турбо Си компилирует исходный код непосредственно в объектный код. Существует несколько способов, с помощью которых можно сообщить Турбо Си, что нужно поддерживать встроенный Ассемблер путем компиляции на язык Ассемблера и последующего вызова утилиты TLINK.

Параметр командной строки -s указывает Турбо Си, что нужно транслировать исходный код в код Ассемблера, после чего прекратить работу. Файл с расширением .ASM, сгенерированный Турбо Си при использовании параметра -s, можно отдельно ассемблировать и скомпоновать с другими модулями Си и Ассемблера.

Параметр -b или вставленная в программу на C директива: #pragma inline указывают Турбо Си, что нужно выполнить трансляцию в Ассемблер, а затем для получения объектного кода вызвать Турбо Ассемблер.

Лучше помещать указание #pragma inline возможно ближе к началу исходного кода языка Си, так как любой исходный код языка Си, после которым следует следует данная директива, будет компилироваться дважды: один раз в обычном режиме, а другой раз в режиме Си -> Ассемблер.

Встроенный код Турбо Ассемблера

Встроенный код Ассемблера может находиться в сегменте кода или сегменте данных Турбо Си. Код Ассемблера, размещенный в функции, ассемблируется в сегмент кода Турбо Си, а встроенный код, размещенный вне функции, ассемблируется в сегмент данных Турбо Си.

Например, программа:

// Таблица квадратов значений
asm SquareLookUpTable   label  word;
asm dw  0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100;

// Функция для поиска квадрата значения между 0 и 10
int LookUpSquare(int Value)
{
asm mov bx,Value; // получить значение для возведения в квадрат
asm shl bx,1; // умножить на 2 для поиска в таблице элементов
              // размером в слово
asm mov ax,[SquareLookUpTable+bx]; // поиск в таблице
return(_AX);
}

помещает данные для таблицы SquareLookUpTable в сегмент данных Турбо Си, а встроенный код Ассемблера в LookUpTable в сегмент кода Турбо Си. С равным успехом данные можно было бы поместить в сегмент кода. Рассмотрим следующую версию LookUpSquare, где SquareLookUpTable находится в сегменте кода Турбо Си:

int LookUpSquare(int Value)
{
  asm  jmp   SkipAroundData /* пропустить таблицу данных */
  asm  SquareLookUpTable   label  word;
  asm  dw  0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100;
SkipAroundData:
  asm  mov bx,Value;
  asm  shl bx,1;
  asm  mov ax,[SquareLookUpTable+bx];
return(_AX);
}

Так как SquareLookUpTable находится в таблице кода Турбо Си, то чтобы из нее можно было считывать, казалось бы требуется использовать префикс переопределения сегмента CS:. Фактически для доступа к SquareLookUpTable данный код автоматически ассемблируется с префиксом CS:. Турбо Си генерирует корректный код Ассемблера, чтобы Турбо Ассемблер знал, к каком сегменте находится SquareLookUpTable, а Турбо Ассемблер затем генерирует необходимые префиксы переопределения сегментов.

Формат встроенных операторов на языке Ассемблера

Встроенные операторы на языке Ассемблера во многом похожи на обычные строки Ассемблера, но начинаются с ключевого слова asm.

Точка с запятой в отличие от других операторов Си в операторах встроенного Ассемблера не является обязательной, хотя она может использоваться для их завершения.

Комментировать код встроенного Ассемблера можно только с помощью комментариев Си.

В поле операнда могут содержаться ссылки на символические имена и на переменные Си.

Обращение к элементам структуры/объединения

Встроенный код Ассемблера может ссылаться непосредственно на элементы структуры. Например, в следующем фрагменте программы:

    .
.
struct Student {
char Teacher[30];
int Grade;
} JohnQPublic;
.
.
asm mov ax,JohnQPublic.Grade;
.
.

в регистр AX загружается содержимое элемента Grade структуры JohnQPublic типа Student.

Встроенный код Ассемблера может также обращаться к элементам структуры, адресуясь к ним относительно базового или индексного регистра. Например:

asm mov bx,OFFSET JohnQPublic;
asm mov ax,[bx].Grade;
.стр AX также загружается элемент Grade структуры JohnQPublic. Поскольку Grade находится в структуре Student по смещению 30, последний пример на самом деле принимает вид:

   asm   mov   bx,OFFSET JohnQPublic;
asm mov ax,[bx]+30

Возможность обращаться к элементам структуры относительно регистра-указателя является достаточно мощным средством, позволяющим во встроенном коде Ассемблера работать с массивами и структурами и использовать указатели на структуры.

Ограничения адресации к операндам в памяти

Единственное изменение, которое Турбо Си вносит в операторы встроенного Ассемблера, состоит в преобразовании ссылок на память и адреса памяти (например, имен переменных и адресов перехода) из их представления в Си в соответствующий эквивалент на Ассемблере.

Такие изменения налагают два ограничения: в инструкциях перехода встроенного Ассемблера можно ссылаться только на метки Си, а в прочих инструкциях можно ссылаться на что угодно, кроме меток языка Си.

Заметим, что вызов подпрограммы не считается переходом, поэтому в качестве операндов при вызове функции Си в инструкции встроенного Ассемблера можно указывать имена функций Си и метки Ассемблера, но не метки Си. Если в коде встроенного Ассемблера имеется ссылка на имя функции Си, то перед именем функции должен указываться символ подчеркивания.

Размер динамических локальных переменных

Когда Турбо Си заменяет в операторе встроенного Ассемблера ссылку на динамическую локальную переменную операндом вида [BP-02], он не помещает в измененный оператор операцию назначения размера (типа WORD PTR или BYTE PTR). Поэтому при обращении к динамической локальной переменной, когда в качестве источника или приемника используется ячейка памяти, нужно указывать размер операнда.

Пример:

int   i;
asm mov WORD PTR i,0;
asm inc WORD PTR i;

Необходимость сохранения регистров

В конце каждого используемого вами кода встроенного Ассемблера регистры BP, CS, DS и SS должны содержать те же значения, которые они имели перед началом выполнения кода встроенного

Ассемблера. Несоблюдение этого правила часто будет приводить к  аварийному завершению программы и перезагрузкам системы. Регистры AX, BX, CX, DX, SI, DI, ES и флаги в коде встроенного Ассемблера  можно свободно изменять.

СПОСОБЫ ПЕРЕДАЧИ ПАРАМЕТРОВ В ПОДПРОГРАММЫЫ

При организации подпрограмм важно выбрать правильный способ передачи параметров. Для микропроцессорных систем разработано несколько таких способов.

Передача параметров через глобальную область данных

Понятие общих (глобальных) данных относится к данным, хранимым в тех ячейках памяти, к которым могут обращаться и вызывающая программа, и подпрограмма. Для этого и программа, и подпрограмма должны знать адрес глобальной области. Этот способ применяется для передачи данных большого объема, но имеет определенные ограничения при совместном использовании ассемблера и языков высокого уровня: объем области глобальных данных не должен превышать 64К байт.

Передача параметров через регистры

Небольшой объем данных может быть передан через регистры общего назначения AX, BX, CX, DX, SI, DI, BP и SP, а также через сегментные регистры DS и ES - всего 10 регистров, через которые можно передать 20 байт информации. Этот способ обеспечивает максимальную эффективность при работе с небольшими объемами данных, так как процессор работает с регистрами значительно быстрее, чем с памятью. Основной недостаток данного способа заключается в жестких ограничениях на число передаваемых параметров.

Рассмотрим участок кода программы, в котором выполняется вызов подпрограммы сложения двух целых чисел с передачей параметров в регистрах AX и BX. Подпрограмма возвращает результат сложения в регистре AX.

Фрагмент кода основной программы:

	.
.br /> mov AX,17
mov BX,7021
call Addition
.
.

Код подпрограммы:

Addition proc near
add AX,BX
ret
Addition endp

Передача параметров через специально выделенную область памяти

Передача параметров может осуществляться через специально выделенную область памяти, называемую областью параметров. Вызывающая программа перед вызовом подпрограммы записывает данные в последовательных ячейках области параметров. Затем начальный адрес этой области передается в подпрограмму через регистры, глобальную область данных или через стек. Иногда передавать параметры в смежных ячейках памяти по каким либо причинам неудобно.

В таких ситуациях целесообразно передавать подпрограмме адрес таблицы, содержащей адреса параметров, либо поместить эту таблицу в глобальную область памяти.

Передача параметров через сегмент кода

Когда программа хранится в ПЗУ, можно разместить область параметров в сегменте кода сразу после команды CALL, а начальный адрес этой области передавать через стек. Когда выполняется команда CALL, адрес возврата, фактически являющийся адресом первого параметра, включается в стек, а управление передается первой команде подпрограммы. Подпрограмма может извлечь из стека этот адрес, последовательно считать параметры, произвести необходимые операции и затем, перед вызовом команды RET модифицировать в стеке адрес возврата так, чтобы он указывал не на область параметров, а на следующую команду программы.

Этот способ применяется крайне редко, так как требует либо применения префикса замены сегмента DS на CS при считывании параметров, либо использования так называемой "малой модели памяти", обеспечивающей совпадение сегментов CS и DS.

Передача параметров через стек

Передача параметров через стек удобна и широко применяется в языках высокого уровня. Вызывающая программа загружает передаваемые параметры в стек перед выполнением команды CALL. Вызываемая подпрограмма получает указатель стека в регистрах SS и SP, а затем копирует содержимое регистра SP в BP и использует адресацию относительно регистровой пары SS:BP для считывания параметров из стека. Копирование SP в BP необходимо, так как указатель стека может изменяться и в самой подпрограмме. После возврата из подпрограммы вызывающая программа должна удалить параметры из стека, изменив содержимое SP.

Рассмотрим пример передачи через стек двух 16-разрядных чисел в подпрограмму, которая выполняет их сложение и возвращает результат в регистре AX.

Фрагмент кода основной программы:

	.
.br /> mov ax,112
push ax
mov ax,361
push ax
call Addition
.
.

Код подпрограммы:

Addition proc near
push bp ; сохранить содержимое BP
mov bp,sp
mov ax,word ptr [bp+4]
add ax,word ptr [bp+6]
pop bp ; восстановить содержимое BP
ret 4 ; удалить параметры из стекаbr />Addition endp

Данная подпрограмма может быть оформлена и без использования регистрс BP:

Addition proc near
mov ax,word ptr [sp+2]
add ax,word ptr [sp+4]
ret 4
Addition endp