Страница 45 из 56
В этом случае будут не только выполняться far-вызовы подп-
рограмм, но и передавемые указатели так же будут far. Модифици-
руйте вашу программу таким образом, чтобы она включала в себя но-
вый файл заголовка:
#include
из стека параметры, так как это сделает вызывающая программа.
Например, программа на ассемблере, созданная транслятором из ис-
ходной программы на Си, для главной функции выглядит так:
mov word ptr [bp-8],5 ; i = 5
mov word ptr [bp-6],7 ; j = 7
mov word ptr [bp-2],0014h ; k = 0x1407AA
mov word ptr [bp-4],07AAh
push word ptr [bp-2] ; Push старший полубайт k
push word ptr [bp-4] ; Push младший полубайт k
push word ptr [bp-6] ; Push j
push word ptr [bp-8] ; Push i
call near ptr funca ; вызов funca (push addr)
add sp,8 ; восстановить стек
Обратим внимание на последнюю команду: add sp,8. Транслятор
знает, сколько параметров помещено в стеке в данный момент; кроме
того он знает, что адрес возврата, помещенный в стек во время вы-
зова funca, уже вынут из него командой ret в конце funca.
Последовательность передачи параметров типа Паскаль
-----------------------------------------------------------------
Другой метод передачи параметров - стандартный метод типа
Паскаль (известный также как Паскаль-соглашение по вызову). Но
- 373,374 -
нельзя сказать, что вы можете вызывать функции Турбо Паскаля из
Турбо Си: это неверно. Рассматриваемая последовательность заносит
параметры слева направо; таким образом, если funca объявлена как
void pascal funca (int p1, int p2, int p3 );
то при вызове этой функции параметры в стек заносятся слева нап-
раво (p1, p2, p3), а следом за ними в стек помещается адрес возв-
рата. Таким образом, если вы даете вызов
main ()
{
int i,j;
long k;
...
i = 5; j = 7; k = 0x1407AA;
funca(i,j,k);
...
}
то стек будет выглядеть так (в момент перед занесением в стек ад-
реса возврата):
SP+06: 0005 i = p1
SP+04: 0007 j = p2
SP+02: 0014
SP: 07AA k = p3
В чем же проявляется отличие такого типа последовательности
передачи параметров от рассмотренного ранее? Кроме изменения по-
рядка занесения параметров, последовательность передачи парамет-
ров типа Паскаль предполагает, что вызванная подпрограмма (funca)
знает, сколько параметров передано и, соответственно, помещено в
стек. Другими словами, подпрограмма вызова funca на ассемблере
теперь выглядит так:
push word ptr[bp-8] ; Push i
push word ptr [bp-6] ; Push j
push word ptr [bp-2] ; Push старший байт k
push word ptr [bp-4] ; Push младший байт k
call near ptr funca ; вызов funca (Push addr)
Заметим, что здесь нет команды add sp,8 после вызова. Вместо
этого funca использует команду ret 8 при завершении для того,
- 375,376 -
чтобы очистить стек перед возвратом к main. По умолчанию все
функции, написанные на Турбо Си, используют метод передачи пара-
метров типа Си. Исключение составляет случай, когда вы используе-
те -р опцию транслятора (соглашение по вызову...Паскаль), когда
все функции используют метод типа Паскаль. В этой ситуации вы мо-
жете заставить нужную функцию применить метод передачи параметров
типа Си с помощью модификатора cdecl:
void cdecl funca (int p1, int p2, int p3);
Этот оператор игнорирует -p опцию.
Зачем вообще нужно Паскаль-соглашение по вызову? По трем ос-
новным соображениям:
- Вы можете быть вызваны подпрограммой, написаной на ассемб-
лере, которая использует данное соглашение по вызову.
- Вы можете быть вызваны подпрограммами, написаными на дру-
гих языках.
- Вызов создаваемой программы будет менее громоздким, пос-
кольку теперь не нужно очищать стек после ее окончания.