Каковы различные соглашения о вызовах в C / C ++ и что они означают? - PullRequest
21 голосов
/ 04 июня 2009

Существуют различные соглашения о вызовах, доступные в C / C ++: stdcall, extern, pascal и т. Д. Сколько таких соглашений о вызовах доступно и что они означают? Есть ли ссылки, которые описывают это?

Ответы [ 6 ]

19 голосов
/ 04 июня 2009

Ни Standard C, ни Standard C ++ не имеют такой концепции - это особенности конкретных компиляторов, компоновщиков и / или операционных систем, поэтому вы должны действительно указать, какие конкретные технологии вас интересуют.

8 голосов
/ 03 июля 2017

Простой ответ: Я использую cdecl, stdcall и fastcall. Я редко пользуюсь fastcall. stdcall используется для вызова функций Windows API.

Подробный ответ (украдено из Википедия ):

cdecl - В cdecl аргументы подпрограммы передаются в стек. Целочисленные значения и адреса памяти возвращаются в регистре EAX, значения с плавающей запятой в регистре ST0 x87. Регистры EAX, ECX и EDX сохраняются вызывающим абонентом, а остальные сохраняются вызываемым абонентом. Регистры с плавающей запятой x87 ST0-ST7 должны быть пустыми (выдвинуты или освобождены) при вызове новой функции, а ST1-ST7 должны быть пустыми при выходе из функции. ST0 также должен быть пустым, если он не используется для возврата значения.

syscall - Это похоже на cdecl в том, что аргументы передаются справа налево. EAX, ECX и EDX не сохраняются. Размер списка параметров в двойных словах передается в AL.

паскаль - параметры помещаются в стек в порядке слева направо (противоположно cdecl), и вызываемый абонент отвечает за балансировку стека перед возвратом.

stdcall - Соглашение о вызовах stdcall [4] представляет собой вариант соглашения о вызовах Pascal, в котором вызываемый объект отвечает за очистку стека, но параметры помещаются в стек справа от в левом порядке, как в соглашении о вызовах _cdecl. Регистры EAX, ECX и EDX предназначены для использования внутри функции. Возвращаемые значения хранятся в регистре EAX.

fastcall - Соглашение __fastcall (также известное как __msfastcall) передает первые два аргумента (вычисляются слева направо), которые соответствуют ECX и EDX. Остальные аргументы помещаются в стек справа налево.

vectorcall - В Visual Studio 2013 Microsoft представила соглашение о вызовах __vectorcall в ответ на проблемы эффективности со стороны разработчиков игр, графики, видео / аудио и кодеков. [7] Для кода IA-32 и x64 __vectorcall аналогичен __fastcall и исходным соглашениям о вызовах x64 соответственно, но расширяет их для поддержки передачи векторных аргументов с использованием регистров SIMD. Для x64, когда любой из первых шести аргументов является векторным типом (float, double, __m128, __m256 и т. Д.), Они передаются через соответствующие регистры XMM / YMM. Аналогично для IA-32, до шести регистров XMM / YMM распределяются последовательно для аргументов векторного типа слева направо независимо от положения. Кроме того, __vectorcall добавляет поддержку для передачи значений однородного векторного агрегата (HVA), которые являются составными типами, состоящими только из четырех идентичных векторных типов, с использованием одних и тех же шести регистров. После того, как регистры были выделены для аргументов векторного типа, неиспользуемые регистры назначаются для аргументов HVA слева направо независимо от положения. Результирующий тип вектора и значения HVA возвращаются с использованием первых четырех регистров XMM / YMM.

safecall - n Delphi и Free Pascal в Microsoft Windows, соглашение о вызовах safecall инкапсулирует обработку ошибок COM (объектная модель компонентов), поэтому исключения не просачиваются вызывающей стороне, а сообщаются в Возвращаемое значение HRESULT, как требуется COM / OLE. При вызове функции safecall из кода Delphi Delphi также автоматически проверяет возвращенный HRESULT и при необходимости вызывает исключение.

Соглашение о вызовах safecall такое же, как и соглашение о вызовах stdcall, за исключением того, что исключения передаются вызывающей стороне в EAX как HResult (а не в FS: [0]), а результат функции передается по ссылке на стек, как если бы это был последний параметр «out». При вызове функции Delphi из Delphi это соглашение о вызовах будет выглядеть так же, как и любое другое соглашение о вызовах, поскольку, хотя исключения передаются обратно в EAX, они автоматически преобразуются вызывающей стороной в надлежащие исключения. При использовании COM-объектов, созданных на других языках, HResults будут автоматически вызываться как исключения, и результат для функций Get будет результатом, а не параметром. При создании COM-объектов в Delphi с помощью safecall не нужно беспокоиться о HResults, поскольку исключения можно вызывать как обычно, но они будут рассматриваться как HResults на других языках.

Соглашение о вызовах Microsoft X64 - Соглашение о вызовах Microsoft x64 [12] [13] соблюдается в Windows и перед загрузкой UEFI (для длительного режима на x86-64). Он использует регистры RCX, RDX, R8, R9 для первых четырех целочисленных аргументов или указателей (в этом порядке), а XMM0, XMM1, XMM2, XMM3 используются для аргументов с плавающей запятой. Дополнительные аргументы помещаются в стек (справа налево). Целочисленные возвращаемые значения (аналогично x86) возвращаются в формате RAX, если 64 или менее бит. Возвращаемые значения с плавающей точкой возвращаются в XMM0. Параметры длиной менее 64 бит не расширены нулями; старшие биты не обнуляются.

При компиляции для архитектуры x64 в контексте Windows (будь то с помощью инструментов Microsoft или сторонних разработчиков) существует только одно соглашение о вызовах - описанное здесь, так что stdcall, thiscall, cdecl, fastcall и т. Д. теперь все одно и то же.

В соглашении о вызовах Microsoft x64 вызывающий обязан выделить 32 байта «теневого пространства» в стеке непосредственно перед вызовом функции (независимо от фактического количества используемых параметров) и извлечь стек после вызов. Теневое пространство используется для разлива RCX, RDX, R8 и R9, [14], но должно быть доступно всем функциям, даже тем, которые имеют менее четырех параметров.

Регистры RAX, RCX, RDX, R8, R9, R10, R11 считаются энергозависимыми (сохраненными вызывающим абонентом). [15]

Регистры RBX, RBP, RDI, RSI, RSP, R12, R13, R14 и R15 считаются энергонезависимыми (сохраненные вызывающим абонентом). [15]

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

В x86-64 Visual Studio 2008 хранит числа с плавающей запятой в XMM6 и XMM7 (а также с XMM8 по XMM15); следовательно, для x86-64 подпрограммы, написанные пользователем на языке ассемблера, должны сохранять XMM6 и XMM7 (по сравнению с x86, где подпрограммам, написанным пользователем, не нужно было сохранять XMM6 и XMM7). Другими словами, пользовательские подпрограммы на ассемблере должны быть обновлены для сохранения / восстановления XMM6 и XMM7 до / после функции при портировании с x86 на x86-64.

5 голосов
/ 04 июня 2009

Стандарт C ++ в основном состоит из двух: extern "C" и extern "C++". Последний является значением по умолчанию; этот бывший используется, когда вам нужно сделать ссылку на C-код. Компиляторы могут определять другие строки, кроме "C" и "C ++". Например, компилятор, который совместим со своим братом Pascal, может определять extern "Pascal".

К сожалению, некоторые компиляторы изобрели ключевые слова вместо этого. В этих случаях см. Документацию компилятора.

2 голосов
/ 04 июня 2009

Это касается того, в каком порядке помещать параметры в стек вызовов, и когда использовать вызов по значению и / или вызов по семантике вызова. Это специфичные для компилятора расширения, предназначенные для упрощения многоязычного программирования.

0 голосов
/ 04 июля 2017

Это специфичные для платформы расширения, необходимые для вызова функций в определенных библиотеках, особенно в Win32 API. Они нестандартны и специфичны для каждого компилятора, хотя параметры MSVC являются стандартом de facto для Windows на x86. Обычно библиотека, которая нуждается в них, объявляет их в заголовочных файлах, и они будут работать прозрачно. Основное различие между ними заключается в том, что в C исторически использовалось менее эффективное соглашение, которое допускало переменное число аргументов любого типа, в то время как Windows и большинство других языков делали это по-разному. Однако многие различия, такие как нажатие левой или правой руки и очистка вызывающей или вызываемой функции, были довольно произвольными.

Они в основном не имеют отношения к 64-битному коду: священные войны за соглашения о вызовах никогда не происходили на этих платформах.

Есть несколько распространенных случаев, когда вам может понадобиться добавить один из них в функцию. Модуль C ++, который должен связываться с модулями, написанными на других языках (и иногда даже другими компиляторами C ++), должен будет использовать соглашение об именовании extern "C" для совместимости. Функция обратного вызова должна использовать то же соглашение о вызовах, что и вызывающая сторона, которая с Windows API CALLBACK, а не по умолчанию. Разделяемой библиотеке может потребоваться экспортировать ее функции с другим соглашением о вызовах, чем для внутреннего использования, или может потребоваться явное использование __cdecl в случае изменения по умолчанию. На некоторых платформах вы можете или не могли бы получить более высокую производительность от __fastcall: это в основном ускоряет короткие конечные функции с одним или двумя параметрами и может замедлять работу некоторых программ.

0 голосов
/ 26 октября 2009

fastcall - оптимизированный, но никто не использует его

...