Existem algumas partes que permitem que todas essas combinações de operadores funcionem da mesma maneira.
A razão fundamental pela qual todos esses trabalhos é que uma função (como foo
) é implicitamente conversível em um ponteiro para a função. É por isso que void (*p1_foo)() = foo;
funciona: foo
é implicitamente convertido em um ponteiro para si mesmo e esse ponteiro é atribuído p1_foo
.
O unário &
, quando aplicado a uma função, gera um ponteiro para a função, assim como gera o endereço de um objeto quando é aplicado a um objeto. Para ponteiros para funções comuns, é sempre redundante devido à conversão implícita de função em função de ponteiro. De qualquer forma, é por isso que void (*p3_foo)() = &foo;
funciona.
O unário *
, quando aplicado a um ponteiro de função, produz a função apontada para, assim como produz o objeto apontado para quando é aplicado a um ponteiro comum a um objeto.
Essas regras podem ser combinadas. Considere o seu penúltimo exemplo **foo
:
- Primeiro,
foo
é implicitamente convertido em um ponteiro para si mesmo e o primeiro *
é aplicado a esse ponteiro de função, produzindo a função foo
novamente.
- Em seguida, o resultado é novamente convertido implicitamente em um ponteiro para si mesmo e o segundo
*
é aplicado, produzindo novamente a função foo
.
- Em seguida, é convertido implicitamente em um ponteiro de função novamente e atribuído à variável.
Você pode adicionar quantos *
s quiser, o resultado é sempre o mesmo. Quanto mais *
s, melhor.
Também podemos considerar seu quinto exemplo &*foo
:
- Primeiro,
foo
é implicitamente convertido em um ponteiro para si mesmo; o unário *
é aplicado, cedendo foo
novamente.
- Em seguida,
&
é aplicado a foo
, produzindo um ponteiro para foo
, que é atribuído à variável.
A &
só pode ser aplicado a uma função no entanto, não a uma função que foi convertido para um ponteiro de função (a menos que, evidentemente, o ponteiro de função é uma variável, caso em que o resultado é um ponteiro-para-um-pointer- para uma função; por exemplo, você pode adicionar à sua lista void (**pp_foo)() = &p7_foo;
).
É por isso &&foo
que não funciona: &foo
não é uma função; é um ponteiro de função que é um rvalue. Contudo,&*&*&*&*&*&*foo
funcionaria da mesma forma &******&foo
, porque nessas duas expressões o &
sempre é aplicado a uma função e não a um ponteiro de função rvalue.
Observe também que você não precisa usar o unário *
para fazer a chamada pelo ponteiro de função; ambos (*p1_foo)();
e (p1_foo)();
têm o mesmo resultado, novamente devido à conversão de função em função de ponteiro.
&foo
pega o endereço defoo
, o que resulta em um ponteiro de função apontando parafoo
, como seria de esperar.&
operadores para objetos: dadoint p;
,&p
gera um ponteiro parap
e é uma expressão de rvalue; o&
operador requer uma expressão lvalue.*
, menos alegre .&*
cancelamento entre si (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."
.Eu acho que também é útil lembrar que C é apenas uma abstração para a máquina subjacente e este é um dos lugares onde essa abstração está vazando.
Do ponto de vista do computador, uma função é apenas um endereço de memória que, se executado, executa outras instruções. Portanto, uma função em C é modelada como um endereço, o que provavelmente leva ao design de que uma função é "a mesma" que o endereço para o qual aponta.
fonte
&
e*
são operações idempotentes em um símbolo declarado como uma função em C, o que significafunc == *func == &func == *&func
e, portanto,*func == **func
Isso significa que o tipo
int ()
é igual aint (*)()
um parâmetro de função e uma função definida pode ser passada como*func
,func
ou&func
.(&func)()
é o mesmo quefunc()
. Link Godbolt.Uma função é realmente um endereço, portanto,
*
e&
não têm significado, e, em vez de produzir um erro, os escolhe compilador para interpretá-lo como o endereço de func.&
em um símbolo declarado como ponteiro de função, no entanto, obterá o endereço do ponteiro (porque agora ele tem uma finalidade separada), enquanto quefuncp
e*funcp
será idênticofonte