Традиционно вызовы функций C выполняются, когда вызывающая сторона помещает некоторые параметры в стек, вызывает функцию и затем выталкивает стек, чтобы очистить эти выдвинутые аргументы.
/* example of __cdecl */
push arg1
push arg2
push arg3
call function
add sp,12 // effectively "pop; pop; pop"
Примечание. Соглашение по умолчанию, показанное выше, известно как __cdecl.
Другим наиболее популярным соглашением является __stdcall. В нем параметры снова выдвигаются вызывающей стороной, но стек очищается вызываемой стороной. Это стандартное соглашение для функций Win32 API (как определено макросом WINAPI в), и его также иногда называют соглашением о вызовах «Pascal».
/* example of __stdcall */
push arg1
push arg2
push arg3
call function // no stack cleanup - callee does this
Это выглядит как незначительная техническая деталь, но если есть разногласия по поводу того, как управляется стек между вызывающим и вызываемым, стек будет уничтожен таким образом, который вряд ли будет восстановлен.
Поскольку __stdcall выполняет очистку стека, (очень крошечный) код для выполнения этой задачи находится только в одном месте, а не дублируется в каждом вызывающем устройстве, как в __cdecl. Это делает код очень немного меньше, хотя влияние размера видно только в больших программах.
Вариативные функции, такие как printf (), почти невозможно понять с помощью __stdcall, потому что только вызывающий объект действительно знает, сколько аргументов было передано для их очистки. Вызываемый может сделать несколько удачных предположений (скажем, просматривая строку формата), но очистка стека должна определяться реальной логикой функции, а не самим механизмом соглашения о вызовах. Следовательно, только __cdecl поддерживает функции с переменным числом, так что вызывающая сторона может выполнить очистку.
Оформление имен символов линкера:
Как упомянуто в пуле выше, вызов функции с «неправильным» соглашением может иметь катастрофические последствия, поэтому у Microsoft есть механизм, позволяющий избежать этого. Это работает хорошо, хотя может быть невыносимым, если не знать, в чем причины.
Они решили разрешить это путем кодирования соглашения о вызовах в имена функций низкого уровня с дополнительными символами (которые часто называются «украшениями»), и компоновщик рассматривает их как несвязанные имена. Соглашение о вызовах по умолчанию - __cdecl, но каждое из них может быть запрошено явно с помощью / G? параметр для компилятора.
__cdecl (cl / Gd ...)
Все имена функций этого типа имеют префикс подчеркивания, и количество параметров на самом деле не имеет значения, потому что вызывающая сторона отвечает за настройку стека и очистку стека. Вызывающий и вызываемый абоненты могут быть сбиты с толку из-за количества фактически переданных параметров, но, по крайней мере, дисциплина стека поддерживается должным образом.
__stdcall (cl / Gz ...)
Этим именам функций предшествует знак подчеркивания, к которому добавляется символ @ плюс количество байтов переданных параметров. С помощью этого механизма невозможно вызвать функцию с «неправильным» типом или даже с неправильным числом параметров.
__ fastcall (кл / гр ...)
Эти имена функций начинаются со знака @ и имеют суффикс @parameter count, очень похожий на __stdcall.
Примеры:
Declaration -----------------------> decorated name
void __cdecl foo(void); -----------------------> _foo
void __cdecl foo(int a); -----------------------> _foo
void __cdecl foo(int a, int b); -----------------------> _foo
void __stdcall foo(void); -----------------------> _foo@0
void __stdcall foo(int a); -----------------------> _foo@4
void __stdcall foo(int a, int b); -----------------------> _foo@8
void __fastcall foo(void); -----------------------> @foo@0
void __fastcall foo(int a); -----------------------> @foo@4
void __fastcall foo(int a, int b); -----------------------> @foo@8