2011-04-07 13 views
15

Estoy tratando de ejecutar el programa siguiente, pero conseguir algunos errores extraños: 1.cC puntero de función de conversión para anular puntero

del archivo:

typedef unsigned long (*FN_GET_VAL)(void); 

FN_GET_VAL gfnPtr; 

void setCallback(const void *fnPointer) 
{ 
    gfnPtr = *((FN_GET_VAL*) (&fnPointer)); 
} 

2.c del archivo:

extern FN_GET_VAL gfnPtr; 

unsigned long myfunc(void) 
{ 
    return 0; 
} 

main() 
{ 
    setCallback((void*)myfunc); 
    gfnPtr(); /* Crashing as value was not properly 
       assigned in setCallback function */ 
} 

Aquí el gfnPtr() se cuelga en suse linux de 64 bits cuando se compila con gcc. Pero está llamando con éxito a gfnPtr() VC6 y SunOS.

Pero si cambio la función como se indica a continuación, está funcionando correctamente.

void setCallback(const void *fnPointer) 
{ 
    int i; // put any statement here 
    gfnPtr = *((FN_GET_VAL*) (&fnPointer)); 
} 

Ayuda con la causa del problema. Gracias.

Respuesta

26

La norma no permite emitir punteros a void*. Solo puede convertir a otro tipo de puntero a función. 6.3.2.3 § 8:

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 otra vez

Es importante destacar, que debe emitir la espalda al tipo original antes de usar el puntero para llamar a la función (técnicamente, a un tipo compatible. Definición de "compatible" en 6.2.7).

+0

Gracias por sus respuestas. Me preocuparía no mezclar datos y punteros de función. Pero en este caso no soy capaz de descubrir la razón por la cual esto está sucediendo. Si compilo y ejecuto con -m32 (32 Bit) funciona perfectamente bien, pero si compilo con -m64 (64Bit) está dando el problema. Además, si agrego una sola instrucción como ** int i; ** por encima de la asignación, está funcionando bien. No estoy seguro de la razón, quizás, de la corrupción, sino de cómo verificarla. – Manoj

9

Tengo tres reglas de oro cuando se trata de punteros de datos y punteros de código:

  • No no mezcla punteros de datos y punteros de código
  • No mezcla punteros de datos y el código punteros
  • No siempre ¡punteros de datos de mezcla y punteros de código!

En la siguiente función:

void setCallback(const void *fnPointer) 
{ 
    gfnPtr = *((FN_GET_VAL*) (&fnPointer)); 
} 

Usted tiene un puntero de datos que causa a un puntero de función. (Sin mencionar que usted hace esto tomando primero la dirección del puntero en sí, colóquelo en un puntero a un puntero, antes de quitarle la referencia).

Trate de reescribir como:

void setCallback(FN_GET_VAL fnPointer) 
{ 
    gfnPtr = fnPointer; 
} 

Además, puede (o debe) dejar caer el molde cuando se ajusta el puntero:

main() 
{ 
    setCallback(myfunc); 
    gfnPtr(); 
} 

Como un bono extra, puede ahora utilizar el verificaciones de tipo normales realizadas por el compilador.

+0

Gracias por sus respuestas. Me preocuparía no mezclar datos y punteros de función. Pero en este caso no soy capaz de descubrir la razón por la que esto está sucediendo. Si compilo y ejecuto con -m32 (32 Bit) funciona perfectamente bien, pero si compilo con -m64 (64Bit) está dando el problema. Además, si agrego una sola instrucción como ** int i; ** por encima de la asignación, está funcionando bien. No estoy seguro de la razón, quizás, de la corrupción, sino de cómo verificarla. – Manoj

+0

+1 para el análisis de que tomar la dirección de un puntero de función no es lo mismo que aplicar '&' al nombre de una función (que es una operación no operativa). –

2

Sugeriré una posible explicación parcial.

@Manoj Si examina (o puede proporcionar) la lista de ensamblaje para SetCallback generada por ambos compiladores, podemos obtener una respuesta definitiva.

En primer lugar, las declaraciones de Pascal Couq son correctas, y Lindydancer muestra cómo configurar correctamente la devolución de llamada. Mi respuesta es solo un intento de explicar el problema real.

Creo que el problema radica en el hecho de que Linux y la otra plataforma usan diferentes modelos de 64 bits (ver 64-bit models on Wikipedia). Tenga en cuenta que Linux usa LP64 (int es 32 bit). Necesitamos más detalles sobre la otra plataforma. Si es SPARC64, usa ILP64 (int es 64 bit).

Según tengo entendido, el problema solo se observó en Linux y desapareció si introdujo una variable local int. ¿Intentó esto con las optimizaciones de vez en cuando? Lo más probable es que este truco no tenga ningún efecto beneficioso con optimizaciones.

En ambos modelos de 64 bits, los punteros deben ser de 64 bits, independientemente de si apuntan a código o datos. Sin embargo, es posible que este no sea el caso (por ejemplo, modelos de memoria segmentada); de ahí, las advertencias de Pascal y Lindydancer.

Si los punteros son del mismo tamaño, lo que queda es un posible problema de alineación de pila. La introducción de un int local (que es de 32 bits en Linux) podría alterar la alineación. Esto solo tendría un efecto si void * y los punteros de función tienen diferentes requisitos de alineación. Un escenario dudoso.

Sin embargo, es probable que los diferentes modelos de memoria de 64 bits sean la causa de lo que usted observó. Le invitamos a proporcionar los listados de ensamblaje para que podamos analizarlos.

+0

La entrada de wikipedia a la que se vincula es incorrecta. Los 64 bits ABI para Solaris en Sparc son LP64 no ILP64. Btw SPARC64 es una marca exclusiva para la implementación de Sparc de Fujitsu. –

12

Desafortunadamente, la norma no permite el fundido entre los punteros de datos y los punteros a las funciones (porque puede que no tenga sentido en algunas plataformas realmente oscuras), aunque POSIX y otros requieren tales conversiones. Una solución no es lanzar el puntero sino lanzar un puntero al puntero (esto está bien para el compilador y hará el trabajo en todas las plataformas normales).

typedef void (*FPtr)(void); // Hide the ugliness 
FPtr f = someFunc;   // Function pointer to convert 
void* ptr = *(void**)(&f); // Data pointer 
FPtr f2 = *(FPtr*)(&ptr); // Function pointer restored 
+0

"porque eso podría no tener sentido en algunas plataformas realmente oscuras" Plataformas realmente oscuras como Linux, Mac OS X, iOS, Microsoft Windows y Android, que tienen Prevención de ejecución de datos. Con Prevención de ejecución de datos, el código y los datos NO son intercambiables. Nota: POSIX requiere que el lanzamiento anule el puntero, pero no requiere que ese puntero resultante sea utilizable, excepto para convertir de nuevo en un puntero de función. –

+0

@JonathanBaldwin Lo que dices solo tiene sentido en plataformas en las que el puntero del código tiene un tamaño diferente al del puntero de datos. Un puntero es solo una dirección y, como tal, nunca está en el área ejecutable. Sin embargo, los datos apuntados por la dirección pueden estar en un área ejecutable. Por lo tanto, es posible que no pueda leer la memoria apuntada por el puntero. Pero ciertamente puede convertirlo desde el puntero de función a vacío * y de vacío * a puntero de función usando este método. A menos que la plataforma tenga un tamaño diferente para los indicadores de función que los punteros de datos, funcionará. – rxantos

+0

@rxantos Claro, un lanzamiento de datos a un puntero de función sería trivial para implementar en un sistema ia32 con DXP, pero no tiene sentido porque dicho puntero se vuelve inutilizable sin devolverlo a un puntero de datos o usar una llamada al sistema perforar agujeros en DXP (es decir, hacer que el área sea ejecutable). En lo que respecta a C, no vale la pena apoyar esto para las plataformas que mencionaste, que incluyen DOS de 16 bits y Windows (¿recuerdas los punteros cercanos y lejanos? Después de todo, si una plataforma, como POSIX, quiere permitir esto directamente, podrían convertirlo en una extensión. –

0

a diferencia de lo que otros dicen, sí se puede tener un puntero void* como un puntero de función, pero la semántica es muy difícil de usar con ella.

Como puede ver, NO NECESITA usarlo como void*, simplemente asígnelo como lo hace normalmente. Ejecuté su código así y lo edité para que funcione.

Archivo1.c:

typedef unsigned long (*FN_GET_VAL)(void); 

extern FN_GET_VAL gfnPtr; 

void setCallback(const void *fnPointer) 
{ 
    gfnPtr = ((FN_GET_VAL) fnPointer); 
} 

Archivo2.c:

int main(void) 
{ 
    setCallback(myfunc); 
    ((unsigned long(*)(void))gfnPtr)(); 
} 
Cuestiones relacionadas