Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ЗФ_ОАиП / Лекции ГГУ Скорины - Программирование.doc
Скачиваний:
179
Добавлен:
21.03.2016
Размер:
2.27 Mб
Скачать

24. Функции с переменным количеством аргументов

24.1. Соглашения о вызовах: модификаторы функций

Во всех языках программирования компилятор формирует ассемблерный код вызова функции одинаково: по ассемблерной команде call процессор запоминает в стеке текущий адрес (адрес текущей команды) и управление передаётся в начало функции. В конце функция исполняет инструкцию ret, которая извлекает сохранённый адрес из стека обратно и дальше процессор продолжает исполнять программу именно с этого адреса. Но вызвать функцию в большинстве случаев мало. Надо ей ещё передать аргументы. Собственно правила их передачи и называют соглашениями о вызовах.

Куда можно «запихнуть» параметры для передачи? Таких мест немного: это регистры процессора и стек. Наиболее общепринятым соглашением считается передача всех параметров через стек. Параметры последовательно заносятся в стек с помощью команды push. Занесли параметры в стек, выполнили функцию, и теперь параметры нужно убрать из стека, чтобы вернуть его в начальное состояние. Кто это должен делать: вызывающая или вызываемая функция?

Соглашения о вызовах (calling convention) – это порядок, в котором параметры функции помещаются в стек, и способ очистки стека при возврате из функции.

Параметры могут помещаться в стек в прямом и в обратном порядке:

  1. прямой порядок – параметры размещаются в стеке в том же порядке, в котором они перечислены в описании функции (в вершине стека всегда находится последний параметр, затем предпоследний и т.д.);

  2. обратный порядок – параметры передаются в порядке от конца к началу (при любом количестве параметров в вершине стека находится сначала первый параметр, за ним второй и т. д.). Это упрощает реализацию функций с неопределённым числом параметров произвольных типов.

Очищать стек может сама вызванная функция, а может – вызывающая функция.

Соглашения о вызовах используется в основном при одновременном использовании функций, написанных на разных языках программирования (в разных языках программирования используются разные соглашения о вызове) и при вызове функций стандартных системных библиотек (например, WinAPI).

Основные соглашения:

__cdecl (C declaration) – основной способ вызова в языке C. Параметры передаются через стек. Параметры помещаются в стек в обратном порядке: помещаются справа налево от последнего к первому, а изымаются из стека от первого к последнему. Таким образом становиться возможно передавать в функцию неопределённое количествово параметров (например, как в printf()). После вызова функции стек очищает тот, кто вызвал функцию, т.е. вызывающая функция.

__pascal – основной способ вызова в языке Паскаль. Параметры передаются через стек. Параметры помещаются в стек в прямом порядке: помещаются слева направо от первого к последнему, а изымаются из стека от последнего к первому. Число параметров фиксировано. После вызова функции стек очищает сама функция.

__stdcall – применяется при вызове функций WinAPI. Параметры передаются через стек. Параметры помещаются в стек в обратном порядке: помещаются справа налево от последнего к первому, а изымаются из стека от первого к последнему. После вызова функции стек очищает сама функция.

__fastcall – так называемый «быстрый вызов», применяется, например, в Delphi и Builder. Аналогичен, stdcall, но параметры (или первые из них, если их много) передаются через регистры процессора. Параметры помещаются в регистры и стек в обратном порядке: помещаются справа налево от последнего к первому, а изымаются из стека от первого к последнему. После вызова функции стек очищает сама функция.

Например, пусть есть вызов функции F(A,B,C);

__cdecl: ; обратный порядок

push C

push B

push A

call F

; ret

inc esp, 12 ; очистка стека здесь

__pascal: ; прямой порядок

push A

push B

push C

call F

; ret 12

; очистка стека - в функции

__stdcall: ; обратный порядок

push C

push B

push A

call F

; ret 12

; очистка стека - в функции

__fastcall: ; обратный порядок, но, например, первый

; параметр передается через регистры

push C

push B

mov edx, A

call F

; ret 8

; очистка стека - в функции

Компилятор языка C умеет работать с разными соглашениями о вызовах. Во-первых, специальными ключами командной строки компилятора (-p/-pc) или опциями среды разработки можно изменить тип соглашения о вызовах, используемого по умолчанию (в BC: Options→Compiler→Entry/Exit Code…→Calling Convention (C/Pascal/Register)), что крайне не рекомендуется делать, поскольку это поменяет тип всех функций (за исключением тех, тип вызова которых указан явно), в том числе и библиотечных, и ваша программа просто перестанет работать. Во-вторых, в объявлении функции можно в явном виде указать с помощью специального ключевого слова, какому соглашению о вызовах она должна следовать. Например:

void __fastcall func(int a, int b);

void __pascal func(int a, int b);

С соглашениями о вызовах теснейшим образом связаны соглашения о внутренних именах функций, которые им присваивает компилятор. На самом деле, в obj-, lib- и dll-файлах функции имеют несколько иные имена, нежели в исходном тексте программы.

Функции внутри программ на языке C распознаются по внутренним именам. Внутреннее имя представляет собой строку, создаваемую компилятором при компиляции прототипа или определения функции. Форма декорирования для функции C зависит от правила вызова, используемого в ее объявлении:

__cdecl – внутреннее имя формируется компилятором с различием верхнего и нижнего регистра добавлением в начало символа подчеркивания (_func);

__pascal – внутреннее имя формируется компилятором приведением имени функции к верхнему регистру (FUNC);

__stdcall – внутреннее имя формируется компилятором с различием верхнего и нижнего регистра добавлением в начало символа подчеркивания, а в конец символа @ и числа байт, занимаемых аргументами (_func@4);

__fastcall – внутреннее имя формируется компилятором с различием верхнего и нижнего регистра добавлением в начало символа @, а в конец символа @ и числа байт, занимаемых аргументами (@func@4).