En lo que respecta al estándar C, si lanza un puntero de función a un puntero de función de un tipo diferente y luego lo llama, es un comportamiento indefinido . Ver Anexo J.2 (informativo):
El comportamiento no está definido en las siguientes circunstancias:
- Un puntero se usa para llamar a una función cuyo tipo no es compatible con el tipo apuntado (6.3.2.3).
La sección 6.3.2.3, párrafo 8 dice:
Un puntero a una función de un tipo puede convertirse en un puntero a una función de otro tipo y viceversa; 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 de función a un tipo de puntero de función diferente, devolverlo de nuevo y llamarlo, y todo funcionará.
La definición de compatible es algo complicada. Se puede encontrar en la sección 6.7.5.3, párrafo 15:
Para que dos tipos de funciones sean compatibles, ambos especificarán tipos de retorno compatibles 127 .
Además, las listas de tipos de parámetros, si ambos 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 deberán tener tipos compatibles. Si un tipo tiene una lista de tipos de parámetros y el otro tipo está especificado por un declarador de función 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 parámetro deberá ser compatible con el tipo que resulta de la aplicación de las promociones de argumento por defecto. Si un tipo tiene una lista de tipos de parámetros y el otro tipo se especifica mediante una definición de función que contiene una lista de identificadores (posiblemente vacía), ambos estarán de acuerdo en el número de parámetros, y el tipo de cada parámetro prototipo será compatible con el tipo que resulte de la aplicación de las promociones de argumentos por defecto al tipo del identificador correspondiente. (En la determinación de la compatibilidad de tipos y de un tipo compuesto, cada parámetro declarado con función o tipo de matriz se considera que tiene el tipo ajustado y cada parámetro declarado con tipo calificado se considera que 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 las citaré aquí porque son bastante extensas, pero puede leerlas en el borrador del estándar C99 (PDF) .
La regla relevante aquí está en la sección 6.7.5.1, párrafo 2:
Para que dos tipos de punteros sean compatibles, ambos deben estar calificados de manera idéntica y ambos deben ser punteros a tipos compatibles.
Por lo tanto, dado que a void*
no es compatible con a struct my_struct*
, un puntero de función de tipo void (*)(void*)
no es compatible con un puntero de función de tipo void (*)(struct my_struct*)
, por lo que esta conversión de punteros de función es un comportamiento técnicamente indefinido.
Sin embargo, en la práctica, en algunos casos puede salirse con la suya sin problemas con los punteros de función de lanzamiento. 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 empujar los argumentos en la pila y hacer un salto indirecto al destino del puntero de función, y obviamente no hay noción de tipos a nivel de código de máquina.
Cosas que definitivamente no puedes hacer:
- Conversión entre punteros de función de diferentes convenciones de llamada. Arruinará la pila y, en el mejor de los casos, se estrellará, en el peor, tendrá éxito en silencio con un enorme agujero de seguridad. En la programación de Windows, a menudo se pasan punteros de función. Win32 espera que todas las funciones de devolución de llamada para utilizar la
stdcall
convención de llamada (que las macros CALLBACK
, PASCAL
y WINAPI
todo se expanden a). Si pasa un puntero de función que usa la convención de llamada estándar de C ( cdecl
), resultará mal.
- En C ++, conversión entre punteros de función de miembro de clase y punteros de función regulares. Esto a menudo hace tropezar a los novatos de C ++. Las funciones miembro de la clase tienen un
this
parámetro oculto , y si convierte una función miembro a una función normal, no hay ningún this
objeto para usar y, nuevamente, resultará en mucha maldad.
Otra mala idea que a veces puede funcionar, pero también es un comportamiento indefinido:
- Conversión entre punteros de función y punteros regulares (por ejemplo, conversión de a
void (*)(void)
a a void*
). Los punteros de función no tienen necesariamente el mismo tamaño que los punteros normales, ya que en algunas arquitecturas pueden contener información contextual adicional. Esto probablemente funcionará bien en x86, pero recuerde que es un comportamiento indefinido.