Problema con los punteros NULL en la plataforma de arquitectura Harvard
harvard-architecture (6)
Tema interesante que nos encontramos aquí esta semana.
Estamos trabajando en C en una plataforma integrada de Harvard Architecture, que tiene direcciones de datos de 16 bits y direcciones de código de 32 bits.
El problema se produce cuando se trabaja con punteros de función. Si tienes codigo como
if (fp) fp();
o
if (fp != 0) fp();
todo esta bien.
Sin embargo si tienes código como
if (fp != NULL) fp();
luego, como NULL
se define como (void *) 0
, el compilador (gcc en este caso) a) no avisa yb) hace una comparación de 16 bits con el puntero de su función en lugar de una comparación de 32 bits. Bien, siempre y cuando el puntero de su función no se encuentre en un límite de 64k, todos los 16 bits inferiores son 0.
En este momento tenemos grandes franjas de código que contienen comprobaciones explícitas contra NULL. La mayoría de ellos serán punteros de datos, pero algunos de ellos serán punteros de función. Un grep rápido para != NULL
o == NULL
reveló más de 3000 resultados, muchos de los cuales se pueden revisar manualmente.
Entonces, lo que nos gustaría ahora sería o
una forma de encontrar todos los casos donde se comparan los punteros de función (pero no los punteros de datos) (por lo que podemos compararlos con FP_NULL, que definiríamos como un 0 de 32 bits), o
redefinir NULL de tal manera que haría lo correcto.
(O, supongo, para actualizar nuestro puerto gcc para detectar y manejar correctamente este caso).
No puedo pensar en ningún enfoque que funcione para 1. El único enfoque que puedo pensar para 2 es redefinir NULL como un puntero de función 0, lo que sería un gran desperdicio para la gran mayoría de las comparaciones que están en contra de los punteros de datos. (Una comparación de 32 bits es de 4 instrucciones, una comparación de 16 bits es de 1 instrucción).
¿Alguna idea o sugerencia?
Aqui hay algunas sugerencias:
cambie temporalmente NULL a
(char*)0
o alguna otra cosa que no sea convertible implícitamente a un puntero de función. Esto debería dar una advertencia sobre cada comparación con un puntero no coincidente. Luego, puede ejecutar la salida del compilador producida a través de una herramienta como grep y buscar un patrón típico para punteros de función, como(*)(
.redefine NULL como
0
(sin que la conversión se anule *). Esta es otra definición válida para NULL y podría hacer lo correcto para usted, pero no garantías.
Edite los encabezados del sistema de la implementación para reemplazar todas las apariciones de
#define NULL ((void *)0)
con
#define NULL 0
Luego presentar un informe de error con el proveedor. No debería tener que modificar su código (perfectamente correcto, aunque de estilo feo) debido a un error en el compilador del proveedor.
La forma en que usted describe debería funcionar:
6.3.2.3/3 Una expresión de constante entera con el valor 0, o una expresión de este tipo convertida para escribir void *, se denomina constante de puntero nulo. Si una constante de puntero nulo se convierte en un tipo de puntero, se garantiza que el puntero resultante, llamado puntero nulo, comparará desigual a un puntero con cualquier objeto o función.
Entonces, o NULL se ha redefinido a algo que no es 0
o (void*)0
(¿o es equivalente?) O su compilador no es conforme.
Intente redefinir NULL (a simple 0
) después de todos los #includes en todos los archivos :-)
solo para patadas: pruebe gcc -E
(para generar la fuente preprocesada) en un archivo de problema y verifique la expansión de NULL
Me parece que la forma más fácil es reemplazar todas las apariciones de NULL
por 0
. Esto funciona para el puntero de función (como usted dice) y los punteros de objeto.
Esta es una variante de (2) Redefinir NULL a 0
simple.
Pero el hecho de que no pueda comparar los punteros de función con NULL
es un error en su implementación. C99 indica que la comparación de la constante de puntero nulo es posible con los punteros de función y de objeto, y que NULL debería expandirse a esta constante.
Pequeña adición de la pregunta 5.8 C-FAQ:
P: ¿Es NULL válido para punteros a funciones?
A: Sí (pero vea la pregunta 4.13 )
Función de mezcla de punteros con (void *) 0
(Una respuesta al comentario de R ..). Creo que el uso de punteros de función y (void *) 0
juntos está bien definido. En mi razonamiento me referiré a las secciones del borrador 1256 de C99, pero no citaré partes grandes para que sea legible. También debe ser aplicable a C89.
- 6.3.2.3 (3) define la expresión constante de entero
0
y tales expresiones convertidas en(void *)
como constante de puntero nula . Y: "Si una constante de puntero nulo se convierte en un tipo de puntero, se garantiza que el puntero resultante, denominado puntero nulo, comparará desigual a un puntero con cualquier objeto o función". - 6.8.9 define los operandos
==
y!=
Para (entre otros) un operando de puntero y una constante de puntero nula. Para estos: "Si un operando es un puntero y el otro es una constante de puntero nulo, la constante de puntero nulo se convierte al tipo de puntero".
Conclusión: En fp == (void *) 0
, la constante del puntero nulo se convierte al tipo de fp
. Este puntero nulo se puede comparar con fp
y se garantiza que es desigual a fp
si apunta a una función. La asignación ( =
) tiene una cláusula similar, entonces fp = (void *) 0;
También está bien definido C.
Podrías probar esto:
#ifdef NULL
#undef NULL
#endif
#define NULL 0
Puedes probar un segmento hack (en realidad solo un hack), así que puedes usar la rápida comparación de 16 bits, sin ningún riesgo. Cree segmentos en cada límite n * 0x10000 con un tamaño de 4 (o incluso más pequeño), para que nunca exista una función real.
Depende de la memoria de su dispositivo integrado, si esta es una buena o una mala solución. Podría funcionar si tienes 1MB normal de Flash, que nunca cambiará. Será doloroso si tienes 64MB Nand Flash.