Почему определения указателей на функции работают с любым количеством амперсандов '&' или звездочек '*'?

Почему следующие работы?

void foo() {
    cout << "Foo to you too!\n";
};

int main() {
    void (*p1_foo)() = foo;
    void (*p2_foo)() = *foo;
    void (*p3_foo)() = &foo;
    void (*p4_foo)() = *&foo;
    void (*p5_foo)() = &*foo;
    void (*p6_foo)() = **foo;
    void (*p7_foo)() = **********************foo;

    (*p1_foo)();
    (*p2_foo)();
    (*p3_foo)();
    (*p4_foo)();
    (*p5_foo)();
    (*p6_foo)();
    (*p7_foo)();
}

5 ответов

Решение

Здесь есть несколько моментов, которые позволяют всем этим комбинациям операторов работать одинаково.

Основная причина, по которой все эти работы заключаются в том, что функция (например, foo) неявно преобразуется в указатель на функцию. Вот почему void (*p1_foo)() = foo; работает: foo неявно преобразуется в указатель на себя, и этот указатель присваивается p1_foo,

Одинарный &при применении к функции выдает указатель на функцию, так же, как он возвращает адрес объекта, когда он применяется к объекту. Для указателей на обычные функции он всегда избыточен из-за неявного преобразования функции в указатель на функцию. В любом случае, вот почему void (*p3_foo)() = &foo; работает.

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

Эти правила могут быть объединены. Рассмотрим ваш второй до последнего примера, **foo:

  • Первый, foo неявно преобразуется в указатель на себя и первый * применяется к указателю этой функции, получая функцию foo снова.
  • Затем результат снова неявно преобразуется в указатель на себя и второй * применяется, снова давая функцию foo,
  • Затем он снова неявно преобразуется в указатель функции и присваивается переменной.

Вы можете добавить как можно больше *Как вам нравится, результат всегда один и тот же. Чем больше *с, веселее.

Мы также можем рассмотреть ваш пятый пример, &*foo:

  • Первый, foo неявно преобразуется в указатель на себя; одинарный * применяется, уступая foo снова.
  • Затем & применяется к fooс указателем на foo, который присваивается переменной.

& может быть применен только к функции, но не к функции, которая была преобразована в указатель функции (если, конечно, указатель функции не является переменной, и в этом случае результат является указателем на указатель на -a-function, например, вы можете добавить в свой список void (**pp_foo)() = &p7_foo;).

Вот почему &&foo не работает: &foo не является функцией; это указатель на функцию, который является rvalue. Тем не мение, &*&*&*&*&*&*foo будет работать, как бы &******&fooпотому что в обоих этих выражениях & всегда применяется к функции, а не к указателю на функцию rvalue.

Обратите внимание, что вам не нужно использовать одинарные * сделать вызов через указатель функции; и то и другое (*p1_foo)(); а также (p1_foo)(); имеют тот же результат, опять же из-за преобразования указателя функции в функцию.

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

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

Если вы все еще не очень уверены в ответе @JamesMcNellis, вот доказательство. Это AST(абстрактное синтаксическое дерево) от компилятора Clang. Абстрактное синтаксическое дерево - это внутреннее представление структуры программы внутри компилятора.

      void func1() {};
void test() {
    func1();
    (*func1)();
    (&func1)();

    void(*func1ptr)(void) = func1;
    func1ptr();
    (*func1ptr)();
    //(&func1ptr)();//error since func1ptr is a variable, &func1ptr is its address which is not callable.
}

AST:

      //func1();
|-CallExpr //call the pointer
| `-ImplicitCastExpr //implicitly convert func1 to pointer
|   `-DeclRefExpr //reference func1

//(*func1)();
|-CallExpr //call the pointer
| `-ImplicitCastExpr //implicitly convert the funtion to pointer
|   `-ParenExpr //parentheses
|     `-UnaryOperator //* operator get function from the pointer
|       `-ImplicitCastExpr //implicitly convert func1 to pointer
|         `-DeclRefExpr //reference func1

//(&func1)();
|-CallExpr //call the pointer
| `-ParenExpr //parentheses
|   `-UnaryOperator //& get pointer from func1
|     `-DeclRefExpr //reference func1

//void(*func1ptr)(void) = func1;
|-DeclStmt //define variable func1ptr
| `-VarDecl //define variable func1ptr
|   `-ImplicitCastExpr //implicitly convert func1 to pointer
|     `-DeclRefExpr  //reference func1

//func1ptr();
|-CallExpr  //call the pointer
| `-ImplicitCastExpr //implicitly convert func1ptr to pointer
|   `-DeclRefExpr //reference the variable func1ptr

//(*func1ptr)();
`-CallExpr //call the pointer 
  `-ImplicitCastExpr //implicitly convert the function to pointer
    `-ParenExpr //parentheses
      `-UnaryOperator //* get the function from the pointer
        `-ImplicitCastExpr //implicitly convert func1ptr to pointer
          `-DeclRefExpr //reference the variable func1ptr

& а также * являются идемпотентными операциями над символом, объявленным как функция в C, что означает func == *func == &func == *&func и поэтому *func == **func

Это означает, что тип int () такой же как int (*)() как параметр функции, а определенная функция может быть передана как *func, func или &func. (&func)() такой же как func(). Ссылка Godbolt.

Функция на самом деле является адресом, поэтому * а также & не имеют значения, и вместо того, чтобы выдавать ошибку, компилятор предпочитает интерпретировать ее как адрес функции func.

& на символ, объявленный как указатель на функцию, однако получит адрес указателя (потому что теперь он имеет отдельное назначение), тогда как funcp а также *funcp будет идентичным

При звонке foo из указателя можно опустить даже круглые скобки и звездочку, так же как прямой вызов функции с ее исходным именем, т. е. (*p1_foo)() эквивалентно pi_foo().

Другие вопросы по тегам