Estoy escribiendo código C para un sistema donde la dirección 0x0000 es válida y contiene E / S de puerto. Por lo tanto, cualquier posible error que acceda a un puntero NULL permanecerá sin ser detectado y, al mismo tiempo, provocará un comportamiento peligroso.
Por esta razón, deseo redefinir NULL para que sea otra dirección, por ejemplo, una dirección que no es válida. Si accedo accidentalmente a dicha dirección, obtendré una interrupción de hardware en la que puedo manejar el error. Resulta que tengo acceso a stddef.h para este compilador, por lo que puedo modificar el encabezado estándar y redefinir NULL.
Mi pregunta es: ¿entrará 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 del estándar que indique que NULL debe ser 0?
Otro problema es que muchos compiladores realizan una inicialización estática poniendo todo en cero, sin importar el tipo de datos. Aunque el estándar dice que el compilador debe establecer los 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 a pesar de que modifiqué audazmente los encabezados del compilador manualmente? Porque sé con certeza que este compilador en particular no accede a la macro NULL cuando realiza una inicialización estática.
mprotect
proteger. O, si la plataforma no tiene ASLR o similar, direcciones más allá de la memoria física de la plataforma. Buena suerte.if(ptr) { /* do something on ptr*/ }
? ¿Funcionará si NULL se define de manera diferente a 0x0?Respuestas:
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
0
constante a un valor de puntero debe resultar en unNULL
puntero (§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 no quiere una dirección cero, yNULL
no es la dirección cero.Sin embargo, con modificaciones (importantes) en el compilador y la biblioteca estándar, no es imposible
NULL
ser representado con un patrón de bits alternativo sin dejar de ser estrictamente conforme con la biblioteca estándar. Sin embargo, no es suficiente simplemente cambiar la definición deNULL
sí mismo, ya que entoncesNULL
se evaluaría como verdadero.Específicamente, necesitaría:
-1
.0
para verificar el valor mágico en su lugar (§6.5.9 / 6)Hay algunas cosas que no tiene que manejar. Por ejemplo:
Después de esto,
p
NO se garantiza que sea un puntero nulo. Solo es necesario manejar asignaciones constantes (este es un buen enfoque para acceder a la dirección cero verdadera). Igualmente:También:
x
no se garantiza que lo sea0
.En resumen, esta misma condición aparentemente fue 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 realizar cambios importantes en tu compilador, y listo :)
Como nota al margen, puede ser 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 de preprocesador -> compilador -> ensamblador -> enlazador, agregaría un preprocesador -> transformación NULL -> compilador -> ensamblador -> enlazador. Entonces podrías hacer transformaciones como:
Esto requeriría un analizador de C completo, así como un analizador de tipos y análisis de definiciones de tipos y declaraciones de variables para determinar qué identificadores corresponden a punteros. Sin embargo, al hacer esto, podría evitar tener que realizar cambios en las partes 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. Por supuesto, es probable que también deba realizar cambios en la biblioteca estándar.
fuente
NULL
.El estándar establece que una expresión de constante entera con valor 0, o una expresión de este
void *
tipo convertida al tipo, es una constante de puntero nulo. Esto significa que(void *)0
siempre es un puntero nulo, pero dadoint i = 0;
,(void *)i
no tiene por qué serlo.La implementación de C consta del compilador junto con sus encabezados. Si modifica los encabezados para redefinir
NULL
, pero no modifica el compilador para corregir inicializaciones estáticas, entonces ha creado una implementación no conforme. Es toda la implementación en conjunto la que tiene un comportamiento incorrecto, y si la rompiste, realmente no tienes a nadie más a quien culpar;)Debe corregir algo más que inicializaciones estáticas, por supuesto: dado un puntero
p
,if (p)
es equivalente aif (p != NULL)
, debido a la regla anterior.fuente
Si usa la biblioteca C std, tendrá problemas con las funciones que pueden devolver NULL. Por ejemplo, la documentación de malloc dice:
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 std a menos que pueda reconstruir toda su cadena de herramientas, incluidas las bibliotecas C std.
También debido al uso de NULL por parte de la biblioteca estándar, si redefine NULL antes de incluir los encabezados estándar, puede sobrescribir una definición NULL listada en los encabezados. Cualquier cosa en línea sería incompatible con los objetos compilados.
En su lugar, definiría su propio NULL, "MYPRODUCT_NULL", para sus propios usos y evitaría o traduciría desde / hacia la biblioteca C std.
fuente
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 esté 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 desreferenciar NULL (asumiendo que en su caso la CPU no está ayudando) es el mismo sin importar cómo se defina NULL, por lo que es más fácil dejar NULL definido como cero, y asegúrese de que cero nunca pueda ser desreferenciado de C.
fuente
*p
,p[]
op()
, por lo que el compilador solo necesita preocuparse por aquellos para proteger el puerto IO 0x0000.Teniendo en cuenta la extrema dificultad de redefinir NULL como lo mencionaron 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, de modo que su puerto IO conocido sea:
Si las direcciones que le preocupan están agrupadas y puede estar seguro de que agregar 1 a la dirección no entrará en conflicto con nada (lo que no debería en la mayoría de los casos), es posible que pueda hacerlo de manera segura. Y luego no necesita preocuparse por reconstruir su cadena de herramientas / std lib y expresiones en el formulario:
seguirá funcionando
Loco, lo sé, pero pensé en tirar la idea por ahí :).
fuente
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, es decir, un entero constante de valor 0 que se puede convertir a (void *).
Para lograr el resultado que desea sin dejar de cumplir, tendrá que modificar (o quizás configurar) su cadena de herramientas, pero es posible.
fuente
Estás buscando problemas. Redefinir
NULL
a un valor no nulo romperá este código:fuente