Почему "(*)" можно опустить в указателе функции в списке параметров функции?

Компиляция с gcc8:

#include <stdio.h>
void some_func(void f1(void), void (*f2)(void))
{
    printf("%d\n", f1);
    printf("%d\n", f2);
}

Дает (только) следующие предупреждения:

<source>:11:14: warning: format '%d' expects argument of type 'int', but argument 2 has type 'void (*)(void)' [-Wformat=]
     printf("%d\n", f1);
<source>:12:14: warning: format '%d' expects argument of type 'int', but argument 2 has type 'void (*)(void)' [-Wformat=]
     printf("%d\n", f2);

Почему тип f1 совпадает с типом f2? Только f2 объявлен как указатель на функцию. Я ожидал бы, что f1 вообще не будет компилироваться, так как он называет тип функции, а не указатель на функцию. Какое правило гласит, что тип функции в списке параметров функции изменяется на указатель на этот тип функции?

Ответ 1

Потому что стандарт (6.7.6.3p8) говорит, что

Объявление параметра как функции, возвращающей тип, должно быть настроено на указатель на функцию, возвращающую тип, как в 6.3.2.1.

Это похоже на то, как параметры массивов настраиваются на указатели (6.7.63.p7), если вы об этом думаете.

void some_func(void (void));
void some_func(void (*)(void));

являются совместимыми объявлениями, такими как:

void other_func(char string[]);
void other_func(char *string);

являются.


Обратите внимание, что корректировка не делает void some_func(void (*)(void) совместимым с void some_other_func(void (**)(void) или void yet_another_func(void (*****)(void) и так далее) что касается функций, объявления на самом деле больше не отражают использование (несмотря на то, что это было намерением автора оригинального языка). В стандартизированном C из-за того, как идентификаторы функций переходят в указатели, и из-за того, что не имеет значения, используете ли вы использовать тип функции или тип указателя на функцию для вызова; вы можете вызывать любую функцию с произвольным числом *:

#include <stdio.h>
int main()
{
    (*puts)("hello world");
    (******puts)("hello world");
    (***&*&*puts)("hello world"); //& cancels a * as per 6.5.3.2p3

    int (*p)(char const*) = puts;
    int (**pp)(char const*) = &p;
    int (***ppp)(char const*) = &pp;

    (**ppp)("hello world"); //at least two asterisks required here
}