pointer c null

c - pointer - Redefiniendo NULL



null pointer c++ (7)

Estoy escribiendo el código C para un sistema donde la dirección 0x0000 es válida y contiene puerto de E / S. Por lo tanto, cualquier posible error que acceda a un puntero NULL permanecerá sin detectar y al mismo tiempo causará un comportamiento peligroso.

Por este motivo, deseo redefinir NULL para que sea otra dirección, por ejemplo, una dirección que no sea válida. Si accidentalmente tengo acceso a dicha dirección, obtendré una interrupción de hardware donde puedo manejar el error. Tengo acceso a stddef.h para este compilador, así que puedo alterar el encabezado estándar y redefinir NULL.

Mi pregunta es: ¿entrará esto en conflicto con el estándar C? Por lo que puedo decir de 7.17 en el estándar, la macro está definida por la implementación. ¿Hay algo en otra parte de la norma que indique que NULL debe ser 0?

Otro problema es que muchos compiladores realizan una inicialización estática configurando todo en cero, sin importar el tipo de datos. Aunque el estándar dice que el compilador debe establecer enteros en cero y los punteros en NULL. Si redefiniera NULL para mi compilador, entonces sé que dicha inicialización estática fallará. ¿Podría considerar eso como un comportamiento incorrecto del compilador aunque modifique los encabezados del compilador con valentía? Porque sé con certeza que este compilador en particular no tiene acceso a la macro NULL al hacer la inicialización estática.


Deje NULL solo y trate IO al puerto 0x0000 como un caso especial, tal vez usando una rutina escrita en ensamblador, y por lo tanto no sujeta a la semántica C estándar. IOW, no redefina NULL, redefina el puerto 0x00000.

Tenga en cuenta que si está escribiendo o modificando un compilador de C, el trabajo requerido para evitar la eliminación de referencias NULL (suponiendo que en su caso la CPU no está ayudando) es el mismo sin importar cómo se define NULL, por lo que es más fácil dejar NULL definido como cero, y asegúrese de que cero no pueda ser desreferenciado de C.


El estándar C no requiere que los punteros nulos estén en la dirección cero de la máquina. SIN EMBARGO, convertir una constante 0 en un valor de puntero debe dar como resultado un puntero NULL (§6.3.2.3 / 3) y evaluar el puntero nulo como un booleano debe ser falso. Esto puede ser un poco incómodo si realmente quiere una dirección cero, y NULL no es la dirección cero.

Sin embargo, con modificaciones (pesadas) en el compilador y en la biblioteca estándar, no es imposible que NULL se represente con un patrón de bits alternativo sin dejar de ser estrictamente conforme a la biblioteca estándar. Sin embargo, no es suficiente simplemente cambiar la definición de NULL , ya que entonces NULL evaluaría a verdadero.

Específicamente, necesitarías:

  • Arregle ceros literales en asignaciones a punteros (o lanzamientos a punteros) para convertirlos en algún otro valor mágico como -1 .
  • Haga arreglos para pruebas de igualdad entre punteros y un entero constante 0 para verificar el valor mágico (§6.5.9 / 6)
  • Haga arreglos para todos los contextos en los que un tipo de puntero se evalúa como un booleano para verificar la igualdad del valor mágico en lugar de buscar cero. Esto se sigue de la semántica de prueba de igualdad, pero el compilador puede implementarlo de manera diferente internamente. Ver §6.5.13 / 3, §6.5.14 / 3, §6.5.15 / 4, §6.5.3.3 / 5, §6.8.4.1 / 2, §6.8.5 / 4
  • Como señaló caf, actualice la semántica para la inicialización de los objetos estáticos (§6.7.8 / 10) y los inicializadores de compuestos parciales (§6.7.8 / 21) para reflejar la nueva representación del puntero nulo.
  • Crea una forma alternativa de acceder a la dirección verdadera cero.

Hay algunas cosas que no debes manejar. Por ejemplo:

int x = 0; void *p = (void*)x;

Después de esto, no se garantiza que p sea ​​un puntero nulo. Solo se deben manejar las asignaciones constantes (este es un buen enfoque para acceder a la dirección verdadera cero). Igualmente:

int x = 0; assert(x == (void*)0); // CAN BE FALSE

También:

void *p = NULL; int x = (int)p;

x no se garantiza que sea 0 .

En resumen, esta misma condición fue aparentemente considerada por el comité de lenguaje C, y se hicieron consideraciones para aquellos que elegirían una representación alternativa para NULL. Todo lo que tienes que hacer ahora es hacer cambios importantes en tu compilador, y listo. Ya terminaste :)

Como nota al margen, es posible implementar estos cambios con una etapa de transformación del código fuente antes del compilador propiamente dicho. Es decir, en lugar del flujo normal del preprocesador -> compilador -> ensamblador -> enlazador, agregaría un preprocesador -> transformación NULA -> compilador -> ensamblador -> enlazador. Entonces podrías hacer transformaciones como:

p = 0; if (p) { ... } /* becomes */ p = (void*)-1; if ((void*)(p) != (void*)(-1)) { ... }

Esto requeriría un analizador C completo, así como un analizador sintáctico y un análisis de tiposdef y declaraciones de variables para determinar qué identificadores corresponden a los punteros. Sin embargo, al hacer esto, podría evitar tener que realizar cambios en las porciones de generación de código del compilador propiamente dicho. clang puede ser útil para implementar esto, entiendo que fue diseñado con transformaciones como esta en mente. También es probable que necesite realizar cambios en la biblioteca estándar, por supuesto.


El estándar establece que una expresión constante entera con valor 0, o una expresión tal convertida al tipo void * , es una constante de puntero nulo. Esto significa que (void *)0 siempre es un puntero nulo, pero dado int i = 0; , (void *)i no necesito ser.

La implementación de C consiste del compilador junto con sus encabezados. Si modifica los encabezados para redefinir NULL , pero no modifica el compilador para corregir las inicializaciones estáticas, entonces ha creado una implementación no conforme. Es toda la implementación en conjunto lo que tiene un comportamiento incorrecto, y si lo rompiste, realmente no tienes a nadie más a quien culpar;)

Debe corregir más que solo inicializaciones estáticas, por supuesto, dado un puntero p , if (p) es equivalente a if (p != NULL) , debido a la regla anterior.


El patrón de bits para el puntero nulo puede no ser el mismo que el patrón de bits para el entero 0. Pero la expansión de la macro NULL debe ser una constante de puntero nulo, que es un entero constante de valor 0 que se puede convertir en (vacío *).

Para lograr el resultado que desea manteniendo su conformidad, tendrá que modificar (o quizás configurar) su cadena de herramientas, pero se puede lograr.


Estás pidiendo problemas. Redefinir NULL a un valor no nulo romperá este código:

if (myPointer) { // myPointer is not null ... }


Si usa la biblioteca C estándar, se encontrará con problemas con funciones que pueden devolver NULL. Por ejemplo, la documentación de malloc dice:

Si la función no pudo asignar el bloque de memoria solicitado, se devuelve un puntero nulo.

Debido a que malloc y las funciones relacionadas ya están compiladas en binarios con un valor NULL específico, si redefine NULL, no podrá usar directamente la biblioteca C estándar a menos que pueda reconstruir toda la cadena de herramientas, incluidas las bibliotecas estándar de C.

Además, debido a que la biblioteca std usa NULL, si redefine NULL antes de incluir encabezados std, puede sobreescribir una definición NULL listada en los encabezados. Cualquier cosa en línea sería inconsistente de los objetos compilados.

En su lugar, definiría tu propio NULL, "MYPRODUCT_NULL", para tus propios usos y evitar o traducir de / a la biblioteca de C std.


Teniendo en cuenta la extrema dificultad de redefinir NULL como lo mencionan otros, tal vez sea más fácil redefinir la desreferenciación para direcciones de hardware conocidas. Al crear una dirección, agregue 1 a cada dirección conocida, para que su puerto IO conocido sea:

#define CREATE_HW_ADDR(x)(x+1) #define DEREFERENCE_HW_ADDR(x)(*(x-1)) int* wellKnownIoPort = CREATE_HW_ADDR(0x00000000); printf("IoPortIs" DEREFERENCE_HW_ADDR(wellKnownIoPort));

Si las direcciones que le interesan están agrupadas y puede estar seguro de que agregar 1 a la dirección no entrará en conflicto con nada (que no debería ocurrir en la mayoría de los casos), podría hacerlo de manera segura. Y luego no necesita preocuparse por la reconstrucción de su herramienta cadena / std lib y expresiones en el formulario:

if (pointer) { ... }

seguirá funcionando

Loco, lo sé, pero pensé que arrojaría la idea por ahí :).