Почему определения указателей на функции работают с любым количеством амперсандов '&' или звездочек '*'?
Почему следующие работы?
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()
.