Старые директивы определения сегментов и Borland C++
Коснемся теперь проблемы организации интерфейса Турбо Ассемблера с кодом языка С++, где используются директивы определения сегментов старого типа (стандартные директивы определения сегментов). Например, если вы замените в модуле DOTOTAL.ASM упрощенные директивы определения сегментов директивами старого типа, то получите следующее:
DGROUP group _DATA,_BSS _DATA segment word public "DATA" EXTRN _Repetitions:WORD ; внешний идентификатор PUBLIC _StartingValue ; доступен для других модулей _StartValue DW 0 _DATA ends _BSS segment word public "BSS" RunningTotal DW ? _BSS ends _TEXT segment byte public "CODE" assume cs:_TEXT.ds:DGROUP,ss:DGROUP 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 _TEXT ENDS END
Данная версия директив определения сегментов не только длиннее, то также и хуже читается. К тому же при использовании в программе на языке С++ различных моделей памяти ее труднее изменять. При организации интерфейса с Borland C++ в общем случае виспользовании старых директив определения сегментов нет никаких преимуществ. Если же вы тем не менее захотите использовать при организации интерфейса с Borland C++ старые директивы определения сегментов, вам придется идентифицировать корректные сегменты, соответствующие используемым в коде на языке С++ моделям памяти.
Простейший способ определения, какие сегментные директивы старых версий должны выбираться для компоновки с той или иной программой Borland С++, заключается в компиляции главного модуля программы на Borland С++ для желаемой модели памяти с параметром -S, что тем самым заставит Borland С++ сгенерировать ассемблерную версию соответствующей программы на Borland С++. В этой версии кодов Си вы сможете найти все старые сегментные директивы, используемые Турбо Cи; просто скопируйте их в вашу ассемблерную часть программы.
Вы также можете посмотреть, как будут выглядеть соответствующие старые директивы, скомпилировав их обычным образом (без параметра -S) и использовав TDUMP - утилиту, поставляемую Турбо Ассемблером, чтобы получить все записи определения сегмента. Используйте следующую командную строку:
tdump -OI segdef module.obj
Значения по умолчанию: когда необходимо загружать сегменты?
В некоторых случаях вызываемые из языка С++ функции Ассемблера могут использовать (загружать) для обращения к данным регистры DS и/или ES. Полезно знать соотношение между значениями сегментных регистров при вызове из Borland C++, так как иногда Ассемблер использует преимущества эквивалентности двух сегментных регистров. Давайте рассмотрим значения сегментных регистров в тот момент, когда функция Ассемблера вызывается из Borland C++, а также соотношения между сегментными регистрами, и случаи, когда в функции Ассемблера требуется загружать один или более сегментных регистров.
При входе в функцию Ассемблера из Borland C++ регистры CS и DS имеют следующие значения, которые зависят от используемой модели памяти (регистр SS всегда используется для сегмента стека, а ES всегда используется, как начальный сегментный регистр):
Значения регистров при входе в Ассемблер из Borland C++Модель | CS | DS |
---|
Крохотная | _TEXT | DGROUP | Малая | _TEXT | DGROUP | Компактная | _TEXT | DGROUP | Средняя | имя_файла_TEXT | DGROUP | Большая | имя_файла_TEXT | DGROUP | Громадная | имя_файла_TEXT | имя_вызывающего_файла_DATA |
Здесь "имя_файла" - это имя модуля на Ассемблере, а "имя_вызывающего_файла" - это имя модуля Borland C++, вызывающего модуль на Ассемблере.
В крохотной модели памяти _TEXT и DGROUP совпадают, поэтому при входе в функцию содержимое регистра CS равно содержимому DS. При использовании крохотной, малой и компактной модели памяти при входе в функцию содержимое SS равно содержимому регистра DS.
Когда же в функции на Ассемблере, вызываемой из программы на языке С++, необходимо загружать сегментный регистр? Отметим для начала, что вам никогда не придется (более того, этого не следует делать) загружать регистры SS или CS: при дальних вызовах, переходах или возвратах регистр CS автоматически устанавливается в нужное значение, а регистр SS всегда указывает на сегмент стека и в ходе выполнения программы изменять его не следует (если только вы не пишете программу, которая "переключает" стеки. В этом случае вам нужно четко понимать, что вы делаете).
Регистр ES вы можете всегда использовать так, как это требуется. Вы можете установить его таким образом, чтобы он указывал на данные с дальним типом обращения, или загрузить в ES сегмент-приемник для строковой функции.
С регистром DS дело обстоит иначе. Во всех моделях памяти Borland C++, кроме сверхбольшой, регистр DS при входе в функцию указывает на статический сегмент данных (DGROUP), и изменять его не следует. Для доступа к данным с дальним типом обращения всегда можно использовать регистр ES, хотя вы можете посчитать, что для этого временно нужно использовать регистр DS (если вы собираетесь осуществлять интенсивный доступ к данным), что исключит необходимость использования в вашей программе множества инструкций с префиксом переопределения сегмента. Например, вы можете обратиться к дальнему сегменту одним из следующих способов:
. . . .FARDATA Counter DW 0 . . . .CODE PUBLIC _AsmFunction _AsmFunction PROC . . . mov ax,@FarData mov es,ax ; ES указывает на сегмент данных с дальним типом обращения inc es:[Counter] ; увеличить значение счетчика . . . _AsmFunction ENDP . . .
или иначе:
. . . .FARDATA Counter DW 0 . . . .CODE PUBLIC _AsmFunction _AsmFunction PROC . . . assume ds:@FarData mov ax,@FarDAta mov ds,ax ; DS указывает на сегмент данных с дальним типом обращения inc [Counter] ; увеличить значение счетчика assume ds:@Data mov ax,@Data mov dx,ax ; DS снова указывает на DGROUP . . . _AsmFunction ENDP . . .
Второй вариант имеет то преимущество, что при каждом обращении к дальнему сегменту данных в нем не требуется переопределение ES:. Если для обращения к дальнему сегменту вы загружаете регистр DS, убедитесь в том, что перед обращением к другим переменным DGROUP вы его восстанавливаете (как это делается в приведенном примере). Даже если в данной функции на Ассемблере вы не обращаетесь к DGROUP, перед выходом из нее все равно обязательно нужно восстановить содержимое DS, так как в Borland C++ подразумевается, что регистр DS не изменялся.
При использовании в функциях, вызываемых из С++, сверхбольшой модели памяти работать с регистром DS нужно несколько по-другому. В сверхбольшой модели памяти Borland C++ совсем не использует DGROUP. Вместо этого каждый модуль имеет свой собственный сегмент данных, который является дальним сегментом относительно всех других модулей в программе (нет совместно используемого ближнего сегмента данных). При использовании сверхбольшой модели памяти на входе в функцию регистр DS должен быть установлен таким образом, чтобы он указывал на этот дальний сегмент данных модуля и не изменялся до конца функции, например:
. . . .FARDATA . . . .CODE PUBLIC _AsmFunction _AsmFunction PROC push ds mov ax,@FarData mov ds,ax . . . pop ds ret _AsmFunction ENDP . . .
Заметим, что исходное состояние регистра DS сохраняется при входе в функцию _AsmFunction с помощью инструкции PUSH и перед выходом восстанавливается с помощью инструкции POP. Даже в сверхбольшой модели памяти Borland C++ требует, чтобы все функции сохраняли регистр DS.
Общедоступные и внешние идентификаторы
Программы Турбо Ассемблера могут вызывать функции С++ и ссылаться на внешние переменные Си. Программы Borland C++ аналогичным образом могут вызывать общедоступные (PUBLIC) функции Турбо Ассемблера и обращаться к переменным Турбо Ассемблера. После того, как в Турбо Ассемблере устанавливаются совместимые с Borland C++ сегменты (как описано в предыдущих разделах), чтобы совместно использовать функции и переменные Borland C++ и Турбо Ассемблера, нужно соблюдать несколько простых правил.
Подчеркивания и язык Си
Если вы пишете на языке Си или С++, то все внешние метки должны начинаться с символа подчеркивания (_). Компилятор Си и С++ вставляет символы подчеркивания перед всеми именами внешних функций и переменных при их использовании в программе на Си/С++ автоматически, поэтому вам требуется вставить их самим только в ассемблерных кодах. Вы должны убедиться, что все ассемблерные обращения к функциям и переменным Си начинаются с символа подчеркивания, и кроме того, вы должны вставить его перед именами всех ассемблерных функций и переменных, которые делаются общими и вызываются из программы на языке Си/С++.
Например, следующая программа на языке Си (link2asm.cpp):
extrn int ToggleFlag(); int Flag; main() { ToggleFlag(); }
правильно компонуется со следующей программой на Ассемблере (CASMLINK.ASM):
.MODEL SMALL .DATA EXTRN _Flag:word .CODE PUBLIC _ToggleFlag _ToggleFlag PROC cmp [_Flag],0 ; флаг сброшен? jz SetFlag ; да, установить его mov [_Flag],0 ; нет, сбросить его jmp short EndToggleFlag ; выполнено SetFlag: mov [_Flag],1 ; установить флаг EndToggleFlag: ret _ToggleFlag ENDP END
При использовании в директивах EXTERN и PUBLIC спецификатора языка Си правильно компонуется со следующей программой на Ассемблере (CSPEC.ASM):
.MODEL SMALL .DATA EXTRN C Flag:word .CODE PUBLIC C ToggleFlag ToggleFlag PROC cmp [Flag],0 ; флаг сброшен? jz SetFlag ; да, установить его mov [Flag],0 ; нет, сбросить его jmp short EndToggleFlag ; выполнено SetFlag: mov [Flag],1 ; установить флаг EndToggleFlag: ret ToggleFlag ENDP END
Примечание: Метки, на которые отсутствуют ссылки в программе не Си (такие, как SetFlag) не требуют предшествующих символов подчеркивания.
Турбо Ассемблер автоматически при записи имен Flag и ToggleFlag в объектный файл поместит перед ними символ подчеркивания.
Различимость строчных и прописные символов в идентификаторах
В именах идентификаторов Турбо Ассемблер обычно не различает строчные и прописные буквы (верхний и нижний регистр). Поскольку в С++ они различаются, желательно задать такое различие и в Турбо Ассемблере (по крайней мере для тех идентификаторов, которые совместно используются Ассемблером и С++). Это можно сделать с помощью параметров /ML и /MX.
Переключатель (параметр) командной строки /ML приводит к тому, что в Турбо Ассемблере во всех идентификаторах строчные и прописные символы будут различаться (считаться различными). Параметр командной строки /MX указывает Турбо Ассемблеру, что строчные и прописные символы (символы верхнего и нижнего регистра) нужно различать в общедоступных (PUBLIC) идентификаторах, внешних (EXTRN) идентификаторах глобальных (GLOBAL) идентификаторах и общих (COMM) идентификаторах. В большинстве случаев следует также использовать параметр /ML.
1 2 3 4 5 6 7 8 9
8 8 8
| |