Соглашение о вызове
Соглашение о вызове (англ. calling convention) — описание технических особенностей вызова подпрограмм, определяющее:
- способы передачи параметров подпрограммам;
- способы вызова (передачи управления) подпрограмм;
- способы передачи результатов вычислений, выполненных подпрограммами, в точку вызова;
- способы возврата (передачи управления) из подпрограмм в точку вызова.
Является частью двоичного интерфейса приложений (англ. application binary interface, ABI).
СоставПравить
Соглашение о вызове описывает следующее:
- способ передачи аргументов в функцию. Варианты:
- аргументы передаются через регистры процессора;
- аргументы передаются через стек;
- смешанные (соответственно, стандартизируется алгоритм, определяющий, что передаётся через регистры, а что — через стек или другую память):
- первые несколько аргументов передаются через регистры; остальные — через стек (небольшие аргументы[1]) или другую память (большие аргументы[2]);
- аргументы небольшого размера[1] передаются через стек, большие аргументы[2] — через другую память;
- порядок размещения аргументов в регистрах и/или стеке. Варианты:
- слева направо или прямой порядок: аргументы размещаются в том же порядке, в котором они перечислены при вызове функции. Достоинство: машинный код соответствует коду на языке высокого уровня;
- справа налево или обратный порядок: аргументы передаются в порядке от конца к началу. Достоинство: упрощается реализация функций, принимающих произвольное число аргументов (например,
printf()
) (так как на вершине стека оказывается всегда первый аргумент);
- код, ответственный за очистку стека:
- код, вызывающий функцию, или вызывающая программа. Достоинство: возможность передачи в функцию произвольного числа аргументов;
- код самой функции или вызываемая функция. Достоинство: уменьшение количества инструкций, необходимых для вызова функции (инструкция для очистки стека записывается в конце кода функции и только один раз);
- конкретные инструкции, используемые для вызова и возврата. Для процессора x86, работающего в защищённом режиме, используются исключительно инструкции
call
иret
; при работе в стандартном режиме используются инструкцииcall near
,call far
иpushf/call far
(для возврата соответственноretn
,retf
иiret
); - способ передачи в функцию указателя на текущий объект (
this
илиself
) в объектно-ориентированных языках. Варианты (для процессора x86, работающего в защищённом режиме):- как первый аргумент;
- через регистр
ecx
илиrcx
;
- код, ответственный за сохранение и восстановление содержимого регистров до и после вызова функции:
- вызывающая функция;
- вызываемая функция;
- список регистров, подлежащих сохранению/восстановлению до/после вызова функции.
Соглашение о вызове может быть описано в документации к ABI архитектуры, в документации к ОС или в документации к компилятору.
ИспользованиеПравить
- Соглашение о вызове выбирается во время оптимизации для увеличения скорости выполнения программы или для уменьшения её размера (уменьшения числа инструкций).
- При вызове функций из системных или сторонних библиотек необходимо применять соглашения о вызовах, выбранное на этапе сборки этих библиотек.
- При анализе машинного кода с целью получения текста программы на языке высокого уровня сгенерированные компилятором типовые прологи[3] и эпилоги[4] позволяют распознать адреса начал и концов функций.
Соглашения о вызовах, используемые на x86 при 32-битной адресацииПравить
- Список неполный, представлены основные из применяемых по сей день соглашений.
Для перечисленных ниже соглашений (кроме cdecl
) перед возвратом значений из функции подпрограмма обязана восстановить значения сегментных регистров, регистров esp
и ebp
. Значения остальных регистров могут не восстанавливаться.
Если размер возвращаемого значения функции не больше размера регистра eax
, возвращаемое значение сохраняется в регистре eax
. Иначе возвращаемое значение сохраняется на вершине стека, а указатель на вершину стека сохраняется в регистре eax
. Если возвращается объект с автодеструктором (любой объект C++ с ненулевым деструктором, строки произвольной длины в Паскале, BSTR в WinAPI и т. д.), вызывающая программа должна корректно уничтожить его.
cdeclПравить
cdecl
(сокращение от англ. c-declaration) — соглашение о вызовах, используемое компиляторами для языка Си (отсюда название).
Аргументы функций передаются через стек, справа налево. Аргументы, размер которых меньше 4 байт, расширяются до 4 байт. Очистку стека производит вызывающая программа. Это основной способ вызова функций с переменным числом аргументов (например, printf()
). Способы получения возвращаемого значения функции приведены в таблице.
Тип | Размер возвращаемого значения, байт | Способ передачи возвращаемого значения | Примечание |
---|---|---|---|
Целое число, указатель | 1, 2, 4 | Через регистр eax
|
Значения, размер которых меньше 4 байт, расширяются до 4 байт |
Целое число | 8 | Через пару регистров edx:eax
|
|
Число с плавающей точкой | 4, 8 | Через регистр st0 (из псевдостека x87, FPU)
|
|
Другие | Больше 8 | Через регистр eax
|
Указатель на структуру данных сохраняется в регистре eax
|
Перед вызовом функции вставляется код, называемый прологом (англ. prolog) и выполняющий следующие действия:
После вызова функции вставляется код, называемый эпилогом (англ. epilog) и выполняющий следующие действия:
- восстановление значений регистров, сохранённых кодом пролога;
- очистка стека (от локальных переменных функции).
pascalПравить
pascal
— соглашение о вызовах, используемое компиляторами для языка Паскаль. Также применялось в ОС Windows 3.x.
Аргументы процедур и функций передаются через стек, слева направо. Указатель на вершину стека (значение регистра esp
) на исходную позицию возвращает вызываемая подпрограмма. Изменяемые параметры передаются только по ссылке. Возвращаемое значение передаётся через изменяемый параметр Result. Параметр Result создаётся неявно и является первым аргументом функции.
stdcall или winapiПравить
stdcall
или winapi
— соглашение о вызовах, применяемое в ОС Windows для вызова функций WinAPI.
Аргументы функций передаются через стек, справа налево. Очистку стека производит вызываемая подпрограмма.
fastcallПравить
fastcall
— общее название соглашений, передающих параметры через регистры (обычно это самый быстрый для выполнения компьютером способ, отсюда и название (здесь приставка "fast" с англ. переводится как "быстрый", а "call" с англ. переводится как "вызов". То есть, дословно переводится как "быстрый вызов")). Если для сохранения всех параметров и промежуточных результатов — регистров недостаточно, то дополнительно используется стек.
Соглашение о вызовах fastcall
не стандартизировано, поэтому используется только для вызова процедур и функций, не экспортируемых из исполняемого модуля и не импортируемых извне.
В компиляторах фирмы Borland для соглашения __fastcall
, называемого также register
[5], параметры передаются слева направо в регистрах eax
, edx
и ecx
и, если параметров больше трёх, в стеке, также слева направо. Исходное значение указателя на вершину стека (значение регистра esp
) возвращает вызываемая подпрограмма.
В 32-разрядной версии компилятора фирмы Microsoft[6], а также в компиляторе GCC[7], соглашение __fastcall
, также называемое __msfastcall
, определяет передачу первых двух параметров слева направо в регистрах ecx
и edx
, а остальные параметры передаются справа налево в стеке. Очистку стека производит вызываемая подпрограмма.
safecallПравить
safecall
— соглашение о вызовах, используемое для вызова методов интерфейсов COM.
Методы интерфейсов COM представляют собой функции, возвращающие тип HRESULT. Код, добавляемый после вызова функции, анализирует возвращаемое значение. При наличии ошибки код записывает код ошибки, сообщение об ошибке и поднимает исключение. Иначе настоящее возвращаемое значение скрывается, вместо него используется параметр, передаваемый в функцию последним по ссылке. Например, можно считать два следующих объявления функции эквивалентными.
// safecall
function DoSomething ( a : DWORD ) : DWORD ; safecall ;
// симуляция safecall
function DoSomething ( a : DWORD ; out Result : DWORD ) : HResult ; stdcall ;
thiscallПравить
thiscall
— соглашение о вызовах, используемое компиляторами для языка C++ при вызове методов классов в объектно-ориентированном программировании.
Аргументы функции передаются через стек, справа налево. Очистку стека производит вызываемая функция. Соглашение thiscall
отличается от stdcall
соглашения только тем, что указатель на объект, для которого вызывается метод (указатель this
), записывается в регистр ecx
[8].
См. такжеПравить
ПримечанияПравить
- ↑ 1 2 Под небольшими аргументами понимаются значения, размер которых меньше или равен размеру регистра процессора. Например, 1, 2 и 4 байта для процессора x86, работающего в 32-битном режиме.
- ↑ 1 2 Под большими аргументами понимаются значения, размер которых больше размера регистра процессора. Например, 8 и более байт для процессора x86, работающего в 32-битном режиме.
- ↑ Пролог (англ. prologue) — код, выполняющий сохранение регистров, передачу аргументов в функцию, размещение локальных переменных в стеке функции.
- ↑ Эпилог (англ. epilogue) — код, выполняющий возврат управления вызывающей функции, очистку стека, восстановление значений регистров, передачу возвращаемого значения функции.
- ↑ Program Control: Register Convention (неопр.). docwiki.embarcadero.com (1 июня 2010). Дата обращения: 27 сентября 2010. Архивировано 20 ноября 2012 года.
- ↑ _fastcall (неопр.). msdn.microsoft.com. Дата обращения: 27 сентября 2010. Архивировано 20 ноября 2012 года.
- ↑ Ohse, Uwe gcc attribute overview: function fastcall (неопр.). ohse.de. Дата обращения: 27 сентября 2010. Архивировано 20 ноября 2012 года.
- ↑ thiscall (C++) (англ.). msdn.microsoft.com.
СсылкиПравить
- Статья на сайте msdn про соглашениях о вызовах, поддерживаемых компилятором Visual Studio.
- Agner Fog. Соглашения о вызовах, используемые разными компиляторами с языка C++ в разных ОС (англ.) (рус.) : журнал. — 2014. — 7 августа.
В статье не хватает ссылок на источники (см. рекомендации по поиску). |