¿Por qué las definiciones de puntero de función funcionan con cualquier cantidad de símbolos '&' o asteriscos '*'?

216

¿Por qué funciona lo siguiente?

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)();
}
Palanqueta
fuente

Respuestas:

224

Hay algunas piezas en esto que permiten que todas estas combinaciones de operadores funcionen de la misma manera.

La razón fundamental por la que todos estos trabajos es que una función (como foo) es implícitamente convertible en un puntero a la función. Por eso void (*p1_foo)() = foo;funciona: foose convierte implícitamente en un puntero a sí mismo y se le asigna ese puntero p1_foo.

El unario &, cuando se aplica a una función, produce un puntero a la función, al igual que produce la dirección de un objeto cuando se aplica a un objeto. Para punteros a funciones ordinarias, siempre es redundante debido a la conversión implícita de función a puntero de función. En cualquier caso, por eso void (*p3_foo)() = &foo;funciona.

El unario *, cuando se aplica a un puntero de función, produce la función apuntada a, al igual que produce el objeto señalado cuando se aplica a un puntero ordinario a un objeto.

Estas reglas se pueden combinar. Considere su penúltimo ejemplo **foo:

  • Primero, foose convierte implícitamente en un puntero a sí mismo y el primero *se aplica a ese puntero de función, produciendo la función foonuevamente.
  • Luego, el resultado nuevamente se convierte implícitamente en un puntero a sí mismo y *se aplica el segundo , obteniendo nuevamente la función foo.
  • Luego se convierte implícitamente en un puntero de función nuevamente y se asigna a la variable.

Puede agregar tantos *s como desee, el resultado es siempre el mismo. *Cuantos más , mejor.

También podemos considerar su quinto ejemplo &*foo:

  • Primero, foose convierte implícitamente en un puntero a sí mismo; *se aplica el unario , cediendo foonuevamente.
  • Luego, &se aplica a foo, produciendo un puntero a foo, que se asigna a la variable.

Sin &embargo, solo se puede aplicar a una función, no a una función que se ha convertido en un puntero de función (a menos, por supuesto, que el puntero de función sea una variable, en cuyo caso el resultado es un puntero a puntero) a una función; por ejemplo, podría agregar a su lista void (**pp_foo)() = &p7_foo;).

Por eso &&foono funciona: &foono es una función; Es un puntero de función que es un valor r. Sin embargo, &*&*&*&*&*&*foofuncionaría, como lo haría &******&foo, porque en ambas expresiones &siempre se aplica a una función y no a un puntero de función de valor.

Tenga en cuenta también que no necesita usar el unario *para hacer la llamada a través del puntero de función; ambos (*p1_foo)();y (p1_foo)();tienen el mismo resultado, nuevamente debido a la conversión de puntero de función a función.

James McNellis
fuente
2
@Jimmy: Esas no son referencias a punteros de función, son solo punteros de función. &footoma la dirección de foo, lo que da como resultado un puntero de función que apunta foo, como cabría esperar.
Dennis Zickefoose
2
Tampoco puede encadenar &operadores para objetos: dado int p;, &pproduce un puntero py es una expresión de valor; El &operador requiere una expresión lvalue.
James McNellis el
12
Estoy en desacuerdo. Cuanto más *, menos alegre .
Seth Carnegie
28
No edite la sintaxis de mis ejemplos. He elegido los ejemplos muy específicamente para demostrar las características del lenguaje.
James McNellis
77
Como nota al margen, el estándar C establece explícitamente que una combinación de &*cancelar entre sí (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

Creo que también es útil recordar que C es solo una abstracción para la máquina subyacente y este es uno de los lugares donde se filtra esa abstracción.

Desde la perspectiva de la computadora, una función es solo una dirección de memoria que, si se ejecuta, realiza otras instrucciones. Entonces, una función en C se modela como una dirección, lo que probablemente lleva al diseño de que una función es "la misma" que la dirección a la que apunta.

madumlao
fuente
0

&y *son operaciones idempotentes en un símbolo declarado como una función en C que significa func == *func == &func == *&funcy por lo tanto*func == **func

Significa que el tipo int ()es el mismo que el de int (*)()un parámetro de función y un func definido se puede pasar como *func, funco &func. (&func)()es el mismo que func(). Enlace Godbolt.

Una función es realmente una dirección, por lo tanto, *y &no tienen ningún significado, y en lugar de producir un error, que escoge el compilador interpretarlo como la dirección de func.

&en un símbolo declarado como un puntero de función sin embargo tomará la dirección del puntero (porque ahora tiene un propósito aparte), mientras que funcpy *funcpserá idéntico

Lewis Kelsey
fuente