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

216

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

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)();
}
Джимми
источник

Ответы:

224

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

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

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

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

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

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

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

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

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

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

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

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

Джеймс МакНеллис
источник
2
@Jimmy: Это не ссылки на указатели функций, это просто указатели функций. &fooпринимает адрес foo, в результате чего указатель на функцию указывает foo, как и следовало ожидать.
Деннис Зикефуз
2
Вы также не можете связать &операторы для объектов: данный int p;, &pвозвращает указатель на pи является выражением rvalue; &оператор требует именующего выражения.
Джеймс МакНеллис
12
Я не согласен. Чем больше *, тем меньше веселья .
Сет Карнеги
28
Пожалуйста, не редактируйте синтаксис моих примеров. Я специально выбрал примеры, чтобы продемонстрировать особенности языка.
Джеймс МакНеллис
7
В качестве примечания, в стандарте C прямо указано, что комбинация &*взаимно компенсирует друг друга (6.5.3.2): "The unary & operator yields the address of its operand."/ - / "If the operand is the result of a unary * operator, neither that operator nor the & operator is evaluated and the result is as if both were omitted, except that the constraints on the operators still apply and the result is not an lvalue.".
Лундин
9

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

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

madumlao
источник
0

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

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

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

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

Льюис Келси
источник