Каково значение и использование __stdcall? - PullRequest
65 голосов
/ 20 августа 2009

Я часто сталкивался с __stdcall в эти дни.

MSDN не очень четко объясняет, что это на самом деле означает, когда и зачем его использовать, если оно вообще есть.

Буду признателен, если кто-нибудь даст объяснение, желательно с одним или двумя примерами.

Ответы [ 9 ]

57 голосов
/ 20 августа 2009

Все функции в C / C ++ имеют определенное соглашение о вызовах. Суть соглашения о вызовах состоит в том, чтобы установить, как данные передаются между вызывающим и вызываемым абонентами и кто отвечает за такие операции, как очистка стека вызовов.

Наиболее популярными соглашениями о вызовах в Windows являются

  • __stdcall, помещает параметры в стек в обратном порядке (справа налево)
  • __cdecl, помещает параметры в стек в обратном порядке (справа налево)
  • __clrcall, Загрузка параметров в стек выражений CLR в порядке (слева направо).
  • __fastcall, хранится в регистрах, затем помещается в стек
  • __thiscall, помещено в стек; этот указатель хранится в ECX

Добавление этого спецификатора к объявлению функции по существу говорит компилятору, что вы хотите, чтобы эта конкретная функция имела это конкретное соглашение о вызовах.

Соглашения о вызовах описаны здесь

Раймонд Чен также сделал длинную серию по истории различных соглашений о вызовах (5 частей), начиная здесь.

55 голосов
/ 30 октября 2014

Традиционно вызовы функций 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
7 голосов
/ 20 августа 2009

__stdcall - соглашение о вызовах: способ определения того, как параметры передаются в функцию (в стеке или в регистрах) и кто отвечает за очистку после возврата функции (вызывающая сторона или вызываемая сторона).

Раймонд Чен написал в блоге об основных соглашениях о вызовах в x86 , а также есть замечательная статья CodeProject .

По большей части вам не нужно беспокоиться о них. Единственный случай, в котором вы должны это делать, - это если вы вызываете библиотечную функцию, которая использует что-то отличное от значения по умолчанию - в противном случае компилятор сгенерирует неправильный код и ваша программа, вероятно, вылетит.

6 голосов
/ 20 августа 2009

К сожалению, нет простого ответа, когда его использовать, а когда нет.

__stdcall означает, что аргументы функции помещаются в стек от первого до последнего. Это в отличие от __cdecl, что означает, что аргументы передаются от последнего к первому, и __fastcall, который помещает первые четыре (я думаю) аргумента в регистры, а остальные идут в стек.

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

3 голосов
/ 20 августа 2009

Указывает соглашение о вызовах для функции. Соглашение о вызовах - это набор правил передачи параметров в функцию: в каком порядке, по адресу или по копии, кто должен очистить параметры (вызывающий или вызываемый) и т. Д.

2 голосов
/ 20 августа 2009

__stdcall обозначает соглашение о вызовах (см. этот PDF для некоторых деталей). Это означает, что он определяет, как аргументы функции выталкиваются и извлекаются из стека и кто несет за это ответственность.

__stdcall является лишь одним из нескольких соглашений о вызовах и используется в WINAPI. Вы должны использовать его, если вы предоставляете указатели функций в качестве обратных вызовов для некоторых из этих функций. В общем случае вам не нужно обозначать какое-либо конкретное соглашение о вызовах в вашем коде, а просто использовать значение по умолчанию для компилятора, за исключением случая, указанного выше (предоставление обратных вызовов для стороннего кода).

1 голос
/ 07 декабря 2017

__stdcall - соглашение о вызовах, используемое для функции. Это сообщает компилятору правила, которые применяются для настройки стека, передачи аргументов и получения возвращаемого значения. Существует ряд других соглашений о вызовах, таких как __cdecl , __ thiscall , __ fastcall и __ naked .

__stdcall - это стандартное соглашение о вызовах для системных вызовов Win32.

Более подробную информацию можно найти в Википедии .

1 голос
/ 30 ноября 2011

Проще говоря, когда вы вызываете функцию, она загружается в стек / регистр. __stdcall - это одно соглашение / путь (сначала правый аргумент, затем левый аргумент ...), __decl - другое соглашение, которое используется для загрузки функции в стек или регистры.

Если вы используете их, вы даете команду компьютеру использовать этот конкретный способ загрузки / выгрузки функции во время компоновки, и, следовательно, вы не получите несоответствие / сбой.

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

1 голос
/ 20 августа 2009

Это соглашение о вызовах, что функции WinAPI должны вызываться правильно. Соглашение о вызовах - это набор правил о том, как параметры передаются в функцию и как возвращаемое значение передается из функции.

Если вызывающий и вызываемый код используют разные соглашения, вы сталкиваетесь с неопределенным поведением (например, такой странно выглядящий сбой ).

Компиляторы C ++ по умолчанию не используют __stdcall - они используют другие соглашения. Поэтому для вызова функций WinAPI из C ++ необходимо указать, что они используют __stdcall - это обычно делается в заголовочных файлах Windoes SDK, и вы также делаете это при объявлении указателей на функции.

...