Соглашение о вызове

Материал из Википедии — свободной энциклопедии
(перенаправлено с «Соглашение вызова»)
Перейти к: навигация, поиск

Соглашение о вызове (англ. calling convention)  — часть двоичного интерфейса приложений (англ. application binary interface, ABI), которая регламентирует технические особенности вызова подпрограммы, передачи параметров, возврата из подпрограммы и передачи результата вычислений в точку вызова.

Состав[править | править вики-текст]

Соглашение о вызове описывает следующее:

  • способ передачи аргументов в функцию. Варианты:
    • аргументы передаются через регистры процессора;
    • аргументы передаются через стек;
    • смешанные (соответственно, стандартизируется алгоритм, определяющий, что передаётся через регистры, а что — через стек или память):
      • первые несколько аргументов передаются через регистры; остальные — через стек (небольшие аргументы[1]) или память (большие аргументы[2]);
      • аргументы небольшого размера[1] передаются через стек, большие аргументы[2] — через память;
  • порядок размещения аргументов в регистрах и/или стеке. Варианты:
    • слева направо или прямой порядок: аргументы размещаются в том же порядке, в котором они перечислены при вызове функции. Достоинство: машинный код соответствует коду на языке высогоко уровня;
    • справа налево или обратный порядок: аргументы передаются в порядке от конца к началу. Достоинство: упрощается реализация функций, принимающих произвольное число аргументов (так как на вершине стека оказывается всегда первый аргумент);
  • код, ответственный за очистку стека:
    • код, вызывающий функцию, или вызывающая функция. Достоинство: возможность передачи в функцию произвольного числа аргументов;
    • код самой функции или вызываемая функция. Достоинство: уменьшение количества инструкций, необходимых для вызова функции (инструкция для очистки стека записывается в конце кода функции и только один раз);
  • конкретные инструкции, используемые для вызова и возврата. 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 будет указывать на него.

cdecl[править | править вики-текст]

Основной способ вызова для Си (отсюда название, сокращение от «c-declaration»). Аргументы передаются через стек, справа налево. Аргументы, размер которых меньше 4-х байт, расширяются до 4-х байт. Очистку стека производит вызывающая программа. Это основной способ вызова функций с переменным числом аргументов (например, printf(…)). Способы получения возвращаемого значения функции приведены в таблице.

Тип Размер возвращаемого значения, байт Способ передачи возвращаемого значения Примечание
Целое число, указатель 1, 2, 4 Через регистр EAX Значения, размер которых меньше 4-х байт, расширяются до 4-х байт
Целое число 8 Через пару регистров EDX:EAX
Число с плавающей точкой 4, 8 Через регистр ST0 (из псевдостека x87, FPU)
Другие Больше 8 Через регистр EAX Передаётся указатель на структуру данных

Перед вызовом функции вставляется код, сохраняющий регистры, используемые внутри неё, и помещающий аргументы в стек (пролог (англ. prolog)). После вызова функции вставляется код, восстанавливающий содержимое регистров и очищающий стек (эпилог (англ. epilog)).

pascal[править | править вики-текст]

Основной способ вызова для Паскаля, также применялся в Windows 3.x. Аргументы передаются через стек, слева направо. Указатель стека на исходную позицию возвращает подпрограмма. Причём, изменяемые параметры передаются только по ссылке, а у функций неявно создаётся дополнительный первый изменяемый параметр Result, через который и возвращается значение.

stdcall/winapi[править | править вики-текст]

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

fastcall[править | править вики-текст]

Передача параметров через регистры, обычно самая быстрая; если все параметры и промежуточные результаты умещаются в регистрах, манипуляции со стеком вообще не нужны.

Fastcall не стандартизирован, поэтому используется только в функциях, которые программа не экспортирует наружу и не импортирует извне.

В компиляторе Borland, для соглашения __fastcall, называемого также register[5], параметры передаются слева направо в eax, edx, ecx и, если параметров больше трёх, в стеке, также слева направо. Указатель стека на исходное значение возвращает вызываемая подпрограмма.

Fastcall Borland применяется по умолчанию в Delphi.

Соглашение __fastcall Microsoft, также называемое __msfastcall, в 32-разрядной версии компилятора Microsoft[6], а также компилятора GCC[7], определяет передачу первых двух параметров слева направо в ecx и edx, а остальные параметры передаются справа налево в стеке. Очистку стека производит вызываемая подпрограмма.

safecall[править | править вики-текст]

Обеспечивает более удобный для использования в распространённых языках высокого уровня способ вызова методов интерфейсов при использовании модели COM.

Все методы интерфейсов COM представляют собой функции, возвращающие код завершения типа HRESULT, который должен анализироваться в месте вызова. Как правило, это не вполне удобно: большинство используемых с этой технологией языков имеют механизмы обработки исключений, и в них удобнее использовать обычные вызовы функций и процедур, возвращающие прикладные значения, а для обработки ошибок применять исключения. Именно такую возможность предоставляет safecall.

Можно считать, что вызов

function DoSomething(a: DWORD): DWORD; safecall;

в действительности представляет собой вызов

function DoSomething(a: DWORD; out Result: DWORD): HResult; stdcall;

причём организованный таким образом, что если возвращённое значение типа HRESULT будет содержать флаг ошибки, то в результате вызова будет сгенерировано исключение с кодом и текстом, соответствующим обнаруженной ошибке.

thiscall[править | править вики-текст]

Используется в компиляторах C++. Обеспечивает передачу аргументов при вызовах методов класса в объектно ориентированной среде. Аргументы передаются через стек, справа налево. Очистку стека производит вызываемая функция, то есть тот же самый stdcall. Указатель (this) на объект, для которого вызывается метод, записывается в регистр ECX [8].

Примечания[править | править вики-текст]

  1. 1 2 Под небольшими аргументами понимаются значения, размер которых меньше или равен размеру регистра процессора. Например, 1, 2 и 4 байта для процессора x86, работающего в 32-битном режиме.
  2. 1 2 Под большими аргументами понимаются значения, размер которых больше размера регистра процессора. Например, 8 и более байт для процессора x86, работающего в 32-битном режиме.
  3. Пролог (англ. prologue) — код, выполняющий сохранение регистров, передачу аргументов в функцию, размещение локальных переменных в стеке функции.
  4. Эпилог (англ. epilogue) — код, выполняющий возврат управления вызывающей функции, очистку стека, восстановление значений регистров, передачу возвращаемого значения функции.
  5. Program Control: Register Convention. docwiki.embarcadero.com (1 июня 2010). Проверено 27 сентября 2010. Архивировано из первоисточника 20 ноября 2012.
  6. __fastcall. msdn.microsoft.com. Проверено 27 сентября 2010. Архивировано из первоисточника 20 ноября 2012.
  7. Ohse, Uwe gcc attribute overview: function fastcall. ohse.de. Проверено 27 сентября 2010. Архивировано из первоисточника 20 ноября 2012.
  8. thiscall (C++) (англ.). msdn.microsoft.com.

См. также[править | править вики-текст]