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 un NULL
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, y NULL
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 de NULL
sí mismo, ya que entonces NULL
se evaluaría como verdadero.
Específicamente, necesitaría:
- Haga arreglos para que los ceros literales en las asignaciones a punteros (o lanzamientos a punteros) se conviertan en algún otro valor mágico como
-1
.
- Organice pruebas de igualdad entre punteros y un entero constante
0
para verificar el valor mágico en su lugar (§6.5.9 / 6)
- Organice todos los contextos en los que un tipo de puntero se evalúa como booleano para verificar la igualdad con el valor mágico en lugar de verificar cero. Esto se deriva de la semántica de las pruebas de igualdad, pero el compilador puede implementarlo internamente de manera diferente. Consulte §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 objetos estáticos (§6.7.8 / 10) e inicializadores compuestos parciales (§6.7.8 / 21) para reflejar la nueva representación de puntero nulo.
- Cree una forma alternativa de acceder a la verdadera dirección cero.
Hay algunas cosas que no tiene que manejar. Por ejemplo:
int x = 0;
void *p = (void*)x;
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:
int x = 0;
assert(x == (void*)0); // CAN BE FALSE
También:
void *p = NULL;
int x = (int)p;
x
no se garantiza que lo sea 0
.
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:
p = 0;
if (p) { ... }
/* becomes */
p = (void*)-1;
if ((void*)(p) != (void*)(-1)) { ... }
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.
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.