En lo que se refiere al estándar C, si emite un puntero a un puntero a función de un tipo diferente y luego lo llama, es comportamiento indefinido. Ver Anexo J.2 (informativo):
El comportamiento no está definido en las siguientes circunstancias:
- Un puntero se utiliza para llamar a una función cuyo tipo no es compatible con la punta-a Tipo (6.3.2.3).
Sección 6.3.2.3, párrafo 8 lee:
Un puntero a una función de un tipo se puede convertir en un puntero a una función de otro tipo y de vuelta; el resultado se comparará igual al puntero original. Si se utiliza un puntero convertido para llamar a una función cuyo tipo no es compatible con el tipo apuntado, , el comportamiento no está definido.
En otras palabras, puede convertir un puntero a un tipo de puntero a función diferente, devolverlo nuevamente y llamarlo, y las cosas funcionarán.
La definición de compatible es algo complicada. Se puede encontrar en la sección 6.7.5.3, párrafo 15:
Durante dos tipos de funciones para que sean compatibles, tanto deberá especificar los tipos de retorno compatible .
Además, las listas de tipos de parámetros, si ambas están presentes, coincidirán en el número de parámetros y en el uso del terminador de puntos suspensivos; los parámetros correspondientes deben tener tipos compatibles. Si un tipo tiene una lista de tipos de parámetros y el otro tipo está especificado por un declarador de funciones que no forma parte de una definición de función y que contiene una lista de identificadores vacía, la lista de parámetros no tendrá un terminador de puntos suspensivos y el tipo de cada El parámetro debe ser compatible con el tipo que resulta de la aplicación de las promociones de argumento predeterminadas . Si un tipo tiene una lista de tipos de parámetros y el otro tipo es especificado por una definición de función que contiene una lista de identificadores (posiblemente vacía), ambos deberán aceptar el número de parámetros y el tipo de cada parámetro prototipo será compatible con el tipo que resulta de la aplicación del argumento predeterminado promociones al tipo del identificador correspondiente. (En la determinación de la compatibilidad de tipo y de un tipo compuesto, cada parámetro declarado con tipo de función o matriz se considera que tiene el tipo ajustado y se considera que cada parámetro declarado con el tipo calificado tiene la versión no calificada de su tipo declarado.)
127) Si ambos tipos de funciones son '' estilo antiguo '', los tipos de parámetros no se comparan.
Las reglas para determinar si dos tipos son compatibles se describen en la sección 6.2.7, y no voy a citar aquí ya que son bastante largo, pero se puede leer en la draft of the C99 standard (PDF).
La regla relevante aquí es en el apartado 6.7.5.1, párrafo 2:
Durante dos tipos de puntero para que sean compatibles, ambos serán idénticamente cualificado y ambos serán punteros a tipos compatibles.
Por lo tanto, desde un void*
no es compatible con un struct my_struct*
, un puntero de función del tipo void (*)(void*)
no es compatible con un puntero de función del tipo void (*)(struct my_struct*)
, por lo que este casting de punteros de función es técnicamente un comportamiento indefinido.
En la práctica, sin embargo, puede salirse con la suya con punteros de función de fundición en algunos casos. En la convención de llamadas x86, los argumentos se insertan en la pila y todos los punteros tienen el mismo tamaño (4 bytes en x86 u 8 bytes en x86_64). Llamar a un puntero de función se reduce a presionar los argumentos en la pila y hacer un salto indirecto al objetivo del puntero de función, y obviamente no hay ninguna noción de tipos en el nivel de código de máquina.
cosas que definitivamente no puede hacer:
- moldeada entre los punteros de función de diferentes convenciones de llamada.Destruirá la pila y, en el mejor de los casos, chocará, en el peor de los casos, triunfará silenciosamente con un enorme agujero de seguridad. En la programación de Windows, a menudo se pasan punteros a las funciones. Win32 espera que todas las funciones de devolución de llamada utilicen la convención de llamadas
stdcall
(a la que se extienden las macros CALLBACK
, PASCAL
y WINAPI
). Si pasa un puntero de función que usa la convención de llamada C estándar (cdecl
), se generará maldad.
- En C++, crea entre punteros de función de miembro de clase y punteros de función normales. Esto a menudo hace tropezar a los novatos en C++. Las funciones miembro de clase tienen un parámetro
this
oculto, y si convierte una función miembro en una función normal, no hay ningún objeto this
que usar, y nuevamente, se generará mucha maldad.
Otra mala idea que a veces podría funcionar, pero es también un comportamiento indefinido:
- Conversiones entre los punteros de función y punteros regulares (por ejemplo, lanzando una
void (*)(void)
a un void*
). Los punteros de función no son necesariamente del mismo tamaño que los punteros regulares, ya que en algunas arquitecturas pueden contener información contextual adicional. Esto probablemente funcionará bien en x86, pero recuerda que es un comportamiento indefinido.
Soy un poco novato, pero ¿qué significa un puntero de función "void (*) (void *)"? ¿Es un puntero a una función que acepta un vacío * como argumento y devuelve void –
@Myke: 'void (* func) (void *)' significa que 'func' es un puntero a una función con una firma de tipo como 'void foo (void * arg)'. Entonces sí, tienes razón. – mk12