La norme C n'exige pas que les pointeurs nuls soient à l'adresse zéro de la machine. CEPENDANT, convertir une 0
constante en valeur de pointeur doit donner un NULL
pointeur (§6.3.2.3 / 3), et évaluer le pointeur nul comme un booléen doit être faux. Cela peut être un peu gênant si vous vraiment ne voulez une adresse zéro, et NULL
n'est pas l'adresse zéro.
Néanmoins, avec des modifications (lourdes) du compilateur et de la bibliothèque standard, il n'est pas impossible de se NULL
faire représenter avec un motif de bits alternatif tout en restant strictement conforme à la bibliothèque standard. Cependant, il ne suffit pas de simplement changer la définition de NULL
lui-même, comme cela NULL
serait alors vrai.
Plus précisément, vous devrez:
- Faites en sorte que les zéros littéraux dans les affectations aux pointeurs (ou les casts aux pointeurs) soient convertis en une autre valeur magique telle que
-1
.
- Organiser des tests d'égalité entre les pointeurs et un entier constant
0
pour vérifier la valeur magique à la place (§6.5.9 / 6)
- Organisez tous les contextes dans lesquels un type de pointeur est évalué comme un booléen pour vérifier l'égalité avec la valeur magique au lieu de vérifier zéro. Cela découle de la sémantique du test d'égalité, mais le compilateur peut l'implémenter différemment en interne. Voir §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
- Comme caf l'a souligné, mettez à jour la sémantique pour l'initialisation des objets statiques (§6.7.8 / 10) et des initialiseurs composés partiels (§6.7.8 / 21) pour refléter la nouvelle représentation de pointeur nul.
- Créez un autre moyen d'accéder à la véritable adresse zéro.
Il y a certaines choses que vous n'avez pas à gérer. Par exemple:
int x = 0;
void *p = (void*)x;
Après cela, il p
n'est PAS garanti d'être un pointeur nul. Seules les affectations constantes doivent être traitées (c'est une bonne approche pour accéder à la véritable adresse zéro). Également:
int x = 0;
assert(x == (void*)0); // CAN BE FALSE
Aussi:
void *p = NULL;
int x = (int)p;
x
n'est pas garanti 0
.
En bref, cette condition même a apparemment été examinée par le comité de langue C, et des considérations ont été faites pour ceux qui choisiraient une représentation alternative pour NULL. Tout ce que vous avez à faire maintenant est d'apporter des modifications majeures à votre compilateur, et hop, vous avez terminé :)
En remarque, il peut être possible d'implémenter ces modifications avec une étape de transformation du code source avant le compilateur proprement dit. Autrement dit, au lieu du flux normal du préprocesseur -> compilateur -> assembleur -> éditeur de liens, vous ajouteriez un préprocesseur -> transformation NULL -> compilateur -> assembleur -> éditeur de liens. Ensuite, vous pouvez faire des transformations comme:
p = 0;
if (p) { ... }
/* becomes */
p = (void*)-1;
if ((void*)(p) != (void*)(-1)) { ... }
Cela nécessiterait un analyseur C complet, ainsi qu'un analyseur de type et une analyse des typedefs et des déclarations de variables pour déterminer quels identificateurs correspondent aux pointeurs. Cependant, en faisant cela, vous pourriez éviter d'avoir à apporter des modifications aux parties de génération de code du compilateur proprement dit. clang peut être utile pour l'implémentation - je comprends qu'il a été conçu avec des transformations comme celle-ci à l'esprit. Vous devrez probablement également apporter des modifications à la bibliothèque standard.
mprotect
sécuriser. Ou, si la plate-forme n'a pas d'ASLR ou autre, des adresses au-delà de la mémoire physique de la plate-forme. Bonne chance.