Я изучаю язык C, и во время обучения я нашел строку кода, которая является совершенно новой и странной для меня void PullDown(char **, int, void (__cdecl **)(void));
Я знаю только о 1-м и 2-м параметрах. Я хочу знать о третьем параметре. какая польза от двух звездочек после __cdecl? Я знаю из этого синтаксиса (type_cast *)
, так что это связано с приведением типов?
Какая польза от __cdecl в аргументах функций в C
Ответы (4)
Третий параметр — указатель на указатель функции. Только с одной звездочкой это будет указатель на функцию.
__cdecl
— это специальный атрибут компилятора, который указывает, что необходимо использовать соглашение о вызовах C. См. эту страницу. Если вы играете только с C или с другим компилятором, то можете его игнорировать.
Может быть, пример полезен:
#include <stdio.h>
void PullDown(char **, int, void (**)(void));
int main(int argc, char **argv)
{
void (*fun)(void);
PullDown(NULL, 0, &fun);
fun();
return 0;
}
void my_function(void)
{
printf("Hello!\n");
}
void PullDown(char **param1, int param2, void (**param3)(void))
{
*param3 = my_function;
}
Он печатает "Привет!"
В примере fun
— это переменная указателя на функцию. Указатель fun
передается вызову функции PullDown()
. Таким образом, PullDown()
может установить указатель my_function()
на fun
.
__cdecl
— это расширение языка C, поддерживаемое компилятором Microsoft. Он явно указывает, что функция должна вызываться с соглашениями о вызовах «cdecl», которые относятся к внутренностям того, как именно должно быть установлено состояние регистров и стека до и после вызова функции, чтобы передавать аргументы и возвращаемые значения. .
В вашем фрагменте кода PullDown
определяется как функция с тремя аргументами, первые два из которых — char **
и int
.
Последний аргумент функции, void (__cdecl **)(void)
, является указателем на указатель на функцию с соглашениями о вызовах cdecl, которая не имеет возвращаемого значения и не принимает аргументов.
Чтобы разбить это объявление, мы можем пока полностью удалить __cdecl
и добавить имя переменной для этого параметра:
void (**param)(void)
Оператор *
в объявлениях указывает, что выражение справа от него является указателем, поэтому это означает, что param
является указателем, а также *param
является указателем (поэтому param
является указателем на указатель). Чтобы понять, на что указывает этот указатель, **param
можно заменить заполнителем foobar
, чтобы получить следующее:
void (foobar)(void)
Теперь это имеет избыточную пару круглых скобок и эквивалентно следующему:
void foobar(void)
Теперь это выглядит как обычное объявление функции, возвращающее void
с аргументом void
(без аргументов и без возвращаемого значения). Поэтому param
является указателем на указатель на функцию с этой сигнатурой.
Наконец, __cdecl
применяется к выражению справа от него, а поскольку **param
представляет функцию, __cdecl
можно добавить слева от **param
, чтобы указать, что эта функция имеет соглашения о вызовах cdecl:
void (__cdecl **param)(void)
У параметра в вашем фрагменте кода просто удалено имя параметра param
точно так же, как оно удалено из char **param
и int param
.
Вообще говоря, соглашения о вызовах cdecl должны использоваться по умолчанию при компиляции кода C и C++ с помощью Visual Studio, поэтому явное указание __cdecl
должно быть излишним. Однако бывают случаи, когда необходимо указать, например, что функция имеет __stdcall
соглашения о вызовах, и важно при работе с указателями функций гарантировать, что функции stdcall вызываются только через __stdcall
указатели на функции, а функции cdecl вызываются только через __cdecl
функцию. указатели (которые должны быть по умолчанию). Попытка вызвать функцию с неправильным соглашением о вызовах, скорее всего, приведет к сбою вашей программы или оставит ее в неопределенном состоянии.
void (*)(void)
— это указатель на функцию типаvoid func (void)
.void (**)(void)
— это указатель на указатель функции. Вероятно, это означает, что вызывающая сторона ожидает, что этот параметр будет записан, чтобы указатель на функцию был передан обратно вызывающей стороне.void (__cdecl **)(void)
— то же самое, но с нестандартным расширением__cdecl
. Это определяет соглашение о вызове функции, т.е. кто отвечает за параметры стека. Если это вызывающая сторона, то используется__cdecl
, если это функция, то используется__stdcall
. Оба этих нестандартных расширения обычно используются в программировании для Windows.В этом случае он определяет соглашение о вызовах для указанной функции.
__cdecl
— это метка стандартного соглашения о вызовах C/C++ (названного, как ни странно, cdecl
). Проще говоря, соглашение о вызовах — это набор правил, описывающих, как вызывать функцию на ассемблере (например, помещать аргументы в регистры/стек, получать результат из регистра EAX/RAX или из какого-либо другого места). Подробнее об этих соглашениях можно прочитать на соответствующей вики-странице.
У вас есть функция PullDown
, которая принимает 3 аргумента, а третий — указатель на указатель на функцию, которая должна удовлетворять соглашениям cdecl
.
void (**)(void)
— это указатель на указатель на функцию с пустым списком параметров, возвращающих значение.__cdecl
- это своего рода ключевое слово соглашения о вызовах, специфичное для вашего компилятора. - person Ian Abbott   schedule 18.06.2018