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

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

Ответы

Ответ 1

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

Ответ 2

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

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

Ответ 3

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

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

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

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

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

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

fastcall - соглашение __fastcall (aka __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 (Component Object Model), поэтому исключения не выдаются вызывающему абоненту, но сообщаются в возвращаемое значение HRESULT, как требуется COM/OLE. При вызове функции safecall из кода Delphi Delphi также автоматически проверяет возвращенный HRESULT и при необходимости создает исключение.

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

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

При компиляции для архитектуры x64 в контексте Windows (независимо от того, используются ли они с помощью инструментов Microsoft или не 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.

Ответ 4

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

Ответ 5

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

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

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

Ответ 6

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