Какая польза от __cdecl в аргументах функций в C

Я изучаю язык C, и во время обучения я нашел строку кода, которая является совершенно новой и странной для меня void PullDown(char **, int, void (__cdecl **)(void)); Я знаю только о 1-м и 2-м параметрах. Я хочу знать о третьем параметре. какая польза от двух звездочек после __cdecl? Я знаю из этого синтаксиса (type_cast *), так что это связано с приведением типов?


person Aux    schedule 18.06.2018    source источник
comment
Вы действительно учитесь на незнакомом ресурсе, если он просто сбрасывает это на вас, не вводя заранее несколько тем.   -  person StoryTeller - Unslander Monica    schedule 18.06.2018
comment
stackoverflow.com/q/14114749/17034   -  person Hans Passant    schedule 18.06.2018
comment
void (**)(void) — это указатель на указатель на функцию с пустым списком параметров, возвращающих значение. __cdecl - это своего рода ключевое слово соглашения о вызовах, специфичное для вашего компилятора.   -  person Ian Abbott    schedule 18.06.2018


Ответы (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.

person SKi    schedule 18.06.2018

__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 функцию. указатели (которые должны быть по умолчанию). Попытка вызвать функцию с неправильным соглашением о вызовах, скорее всего, приведет к сбою вашей программы или оставит ее в неопределенном состоянии.

person Candy Gumdrop    schedule 18.06.2018

  • void (*)(void) — это указатель на функцию типа void func (void).

  • void (**)(void) — это указатель на указатель функции. Вероятно, это означает, что вызывающая сторона ожидает, что этот параметр будет записан, чтобы указатель на функцию был передан обратно вызывающей стороне.

  • void (__cdecl **)(void) — то же самое, но с нестандартным расширением __cdecl. Это определяет соглашение о вызове функции, т.е. кто отвечает за параметры стека. Если это вызывающая сторона, то используется __cdecl, если это функция, то используется __stdcall. Оба этих нестандартных расширения обычно используются в программировании для Windows.

    В этом случае он определяет соглашение о вызовах для указанной функции.

person Lundin    schedule 18.06.2018

__cdecl — это метка стандартного соглашения о вызовах C/C++ (названного, как ни странно, cdecl). Проще говоря, соглашение о вызовах — это набор правил, описывающих, как вызывать функцию на ассемблере (например, помещать аргументы в регистры/стек, получать результат из регистра EAX/RAX или из какого-либо другого места). Подробнее об этих соглашениях можно прочитать на соответствующей вики-странице.

У вас есть функция PullDown, которая принимает 3 аргумента, а третий — указатель на указатель на функцию, которая должна удовлетворять соглашениям cdecl.

person popzxc    schedule 18.06.2018