Por que as definições de ponteiro de função funcionam com qualquer número de "e" comercial ou "ou" asteriscos "*"?

216

Por que o seguinte trabalho?

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)();
}
Jimmy
fonte

Respostas:

224

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 foonovamente.
  • 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 foonovamente.
  • 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 &&fooque não funciona: &foonã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.

James McNellis
fonte
2
@ Jimmy: Essas não são referências a indicadores de função, são apenas indicadores de função. &foopega o endereço de foo, o que resulta em um ponteiro de função apontando para foo, como seria de esperar.
Dennis Zickefoose
2
Você também não pode encadear &operadores para objetos: dado int p;, &pgera um ponteiro para pe é uma expressão de rvalue; o &operador requer uma expressão lvalue.
James McNellis 01/08/19
12
Discordo. Quanto mais *, menos alegre .
Seth Carnegie
28
Por favor, não edite a sintaxe dos meus exemplos. Eu escolhi os exemplos muito especificamente para demonstrar os recursos do idioma.
James McNellis
7
Como observação lateral, o padrão C declara explicitamente que uma combinação de &*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.".
Lundin
9

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.

madumlao
fonte
0

&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 a int (*)()um parâmetro de função e uma função definida pode ser passada como *func, funcou &func. (&func)()é o mesmo que func(). 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 que funcpe *funcpserá idêntico

Lewis Kelsey
fonte