Компоновка ассемблерных модулей с С++
Важной концепцией С++ является безопасная с точки зрения стыковки типов компоновка. Компилятор и компоновщик должны работать согласованно, чтобы гарантировать правильность типов передаваемых между функциями аргументов. Процесс, называемый "корректировкой имен" (name-mangling), обеспечивает необходимую информацию о типах аргументов. "Корректировка имени" модифицирует имя функции таким образом, чтобы оно несло информацию о принимаемых функцией аргументах.
Когда программа пишется целиком на С++, корректировка имен происходит автоматически и прозрачно для программы. Однако, когда вы пишете ассемблерный модуль для последующей его компоновки с программой на С++, вы сами обязаны обеспечить корректировку имен в модуле. Это легко сделать, написав пустую функцию на С+ и скомпилировав ее с ассемблерным модулем. Генерируемый при этом Borland С++ файл .ASM будет содержать исправленные имена. Затем вы можете их использовать при написании реального ассемблерного модуля.
Например, следующий фрагмент кода определяет четыре различные версии функции с именем test:
void test() { }
void test( int ) { }
void test( int, int ) { }
void test( float, double ) { }
Если этот код компилируется с параметром -S, то компилятор создает на выходе файл на языке Ассемблера (.ASM). Вот как он выглядит (несущественные детали убраны):
; void test() @testSqv proc near push bp mov bp,sp popo bp ret @testSqv endp
; void test( int ) @testSqi proc near push bp mov bp,sp popo bp ret @testSqi endp
; void test( int, int ) @testSqii proc near push bp mov bp,sp popo bp ret @testSqii endp
; void test( float, double ) @testSqfd proc near push bp mov bp,sp popo bp ret @testSqfd endp
Использование Extern "C" для упрощения компоновки
При желании вы можете использовать в ассемблерных функциях неисправленные имена, не пытаясь выяснить, как должны выглядеть правленные. Использование нескорректированных имен защитит ваши ассемблерные функции от возможных изменений алгоритма в будущем. Borland С++ позволяет определять в программах С++ стандартные имена функций С++, как в следующем примере:
extern "C" { int add(int *a, int b); }
Любые функции, объявленные внутри фигурных скобок, получат имена в стиле языка Си. Ниже показаны соответствующие определения в ассемблерном модуле:
public _add _add proc
Объявление ассемблерной функции в блоке extern "C" позволит вам избежать проблем со "откорректированными именами". При этом улучшится и читаемость кода.
Модели памяти и сегменты
Чтобы данная функция Ассемблера могла могла вызываться из С++, она должна использовать ту же модель памяти, что и программа на языке С++, а также совместимый с С++ сегмент кода. Аналогично, чтобы данные, определенные в модуле Ассемблера, были доступны в программе на языке С++ (или данные С++ были доступны в программе Ассемблера), в программе на Ассемблере должны соблюдаться соглашения языка С++ по наименованию сегмента данных.
Модели памяти и обработку сегментов на Ассемблере может оказаться реализовать довольно сложно. К счастью, Турбо Ассемблер сам выполняет почти всю работу по реализации моделей памяти и сегментов, совместимых с Borland C++, при использовании упрощенных директив определения сегментов.
Упрощенные директивы определения сегментов и Borland C++
Директива .MODEL указывает Турбо Ассемблеру, что сегменты, создаваемые с помощью упрощенных директив определения сегментов, должны быть совместимы с выбранной моделью памяти (TINY - крохотной, SMALL - малой, COMPACT - компактной, MEDIUM - средней, LARGEбольшой или HUGE - громадной) и управляет назначаемым по умолчанию типом (FAR или NEAR) процедур, создаваемых по директиве PROC. Модели памяти, определенные с помощью директивы .MODEL, совместимы с моделями Borland C++ с соответствующими именами.
Наконец, упрощенные директивы определения сегментов .DATA, .CODE, .DATA?, .FARDATA, .FARDATA? и .CONST генерируют сегменты, совместимые с Borland C++.
Например, рассмотрим следующий модуль Турбо Ассемблера с именем DOTOTAL.ASM:
.MODEL SMALL ; выбрать малую модель памяти (ближний код и данные) .DATA ; инициализация сегмента данных, совместимого с Borland C++ EXTRN _Repetitions:WORD ; внешний идентификатор PUBLIC _StartingValue ; доступен для других модулей _StartValue DW 0 .DATA? ; инициализированный сегмент данных, совместимый с Borland C++ RunningTotal DW ? .CODE ; сегмент кода, совместимый с Borland C++ PUBLIC _DoTotal _DoTotal PROC ; функция (в малой модели памяти вызывается с помощью вызова ближнего типа) mov cx,[_Repetitions] ; счетчик выполнения mov ax,[_StartValue] mov [RunningTotal],ax ; задать начальное ; значение TotalLoop: inc [RunningTotal] ; RunningTotal++ loop TotalLoop mov ax,[RunningTotal] ; возвратить конечное значение (результат) ret _DoTotal ENDP END
Написанная на Ассемблере процедура _DoTotal при использовании малой модели памяти может вызываться из Borland C++ с помощью оператора:
DoTotal();
Заметим, что в процедуре DoTotal предполагается, что где-то в другой части программы определена внешняя переменная Repetitions. Аналогично, переменная StartingValue объявлена, как общедоступная, поэтому она доступна в других частях программы. Следующий модуль Borland C++ (который называется SHOWTOT.CPP) обращается к данным в DOTOTAL.ASM и обеспечивает для модуля DOTOTAL.ASM внешние данные:
extern int StartingValue; extern int DoTotal(word); int Repetitions; main() { int i; Repetitions = 10; StartingValue = 2; print("%d\n", DoTotal()); }
Чтобы создать из модулей DOTOTAL.ASM и SHOWTOT.CPP выполняемую программу SHOWTOT.EXE, введите команду:
bcc showtot.cpp dototal.asm
Если бы вы захотели скомпоновать процедуру _DoTotal с программой на языке C++, использующей компактную модель памяти, то пришлось бы просто заменить директиву .MODEL на .MODEL COMPACT, а если бы вам потребовалось использовать в DOTATOL.ASM сегмент дальнего типа, то можно было бы использовать директиву .FARDATA.
Короче говоря, при использовании упрощенных директив определения сегментов генерация корректного упорядочивания сегментов, моделей памяти и имен сегментов труда не составляет.
1 2 3 4 5 6 7 8 9
8 8 8
|