119 votes

Redéfinir la notion de NULL

J'écris du code C pour un système où l'adresse 0x0000 est valide et contient des entrées/sorties de port. Par conséquent, tout bogue éventuel qui accède à un pointeur NULL ne sera pas détecté et provoquera en même temps un comportement dangereux.

Pour cette raison, je souhaite redéfinir NULL pour qu'il soit une autre adresse, par exemple une adresse qui n'est pas valide. Si j'accède accidentellement à une telle adresse, j'obtiendrai une interruption matérielle où je pourrai traiter l'erreur. Il se trouve que j'ai accès à stddef.h pour ce compilateur, donc je peux modifier l'en-tête standard et redéfinir NULL.

Ma question est la suivante : cela va-t-il entrer en conflit avec la norme C ? D'après ce que je peux voir dans la norme 7.17, la macro est définie par l'implémentation. Y a-t-il quelque chose d'autre dans la norme qui stipule que NULL doit être 0 ?

Un autre problème est que de nombreux compilateurs effectuent une initialisation statique en mettant tout à zéro, quel que soit le type de données. Même si la norme indique que le compilateur doit mettre les entiers à zéro et les pointeurs à NULL. Si je redéfinis NULL pour mon compilateur, je sais qu'une telle initialisation statique échouera. Puis-je considérer cela comme un comportement incorrect du compilateur, même si j'ai audacieusement modifié manuellement les en-têtes du compilateur ? Parce que je sais avec certitude que ce compilateur particulier n'accède pas à la macro NULL lors de l'initialisation statique.

84voto

bdonlan Points 90068

La norme C n'exige pas que les pointeurs nuls soient à l'adresse zéro de la machine. CEPENDANT, le casting d'un 0 à une valeur de type pointeur doit donner lieu à une valeur de type NULL (§6.3.2.3/3), et l'évaluation du pointeur nul en tant que booléen doit être fausse. Cela peut être un peu gênant si l'on souhaite vraiment faire veulent une adresse zéro, et NULL n'est pas l'adresse zéro.

Néanmoins, en apportant des modifications (lourdes) au compilateur et à la bibliothèque standard, il n'est pas impossible de disposer de NULL être représenté par une autre configuration binaire tout en restant strictement conforme à la bibliothèque standard. Il est pas Il suffit de modifier la définition de NULL cependant, comme à l'époque NULL serait évalué à true.

Plus précisément, vous devrez :

  • Faites en sorte que les zéros littéraux dans les affectations aux pointeurs (ou les casts vers les pointeurs) soient convertis en une autre valeur magique telle que -1 .
  • Prévoir des tests d'égalité entre les pointeurs et un entier constant 0 pour vérifier la valeur magique à la place (§6.5.9/6)
  • Faire en sorte que tous les contextes dans lesquels un type de pointeur est évalué comme un booléen vérifient l'égalité avec la valeur magique au lieu de vérifier la présence de zéro. Cela découle de la sémantique des tests 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 l'a souligné Caf, il faut mettre à jour la sémantique de l'initialisation des objets statiques (§6.7.8/10) et des initialisateurs composés partiels (§6.7.8/21) pour refléter la nouvelle représentation des pointeurs nuls.
  • Créez un autre moyen d'accéder à la véritable adresse zéro.

Il y a certaines choses que vous faites pas doivent gérer. Par exemple :

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

Après ça, p n'est PAS garanti comme étant un pointeur nul. Seules les affectations constantes doivent être gérées (c'est une bonne approche pour accéder à la véritable adresse zéro). De même :

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

Aussi :

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

x n'est pas garanti comme étant 0 .

En bref, cette condition a apparemment été prise en compte par le comité du langage C, et des considérations ont été faites pour ceux qui choisiraient une représentation alternative pour NULL. Il ne vous reste plus qu'à apporter des modifications majeures à votre compilateur, et voilà, vous avez terminé :)

Accessoirement, il peut être possible d'implémenter ces changements avec une étape de transformation du code source avant le compilateur proprement dit. C'est-à-dire qu'au lieu du flux normal de préprocesseur -> compilateur -> assembleur -> éditeur de liens, vous ajouteriez un préprocesseur -> transformation NULL -> compilateur -> assembleur -> éditeur de liens. Alors vous pourriez faire des transformations comme :

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

Cela nécessiterait un analyseur syntaxique C complet, ainsi qu'un analyseur syntaxique de type et une analyse des typedefs et des déclarations de variables pour déterminer quels identifiants correspondent aux pointeurs. Cependant, en faisant cela, vous pourriez éviter d'avoir à apporter des modifications aux portions de génération de code du compilateur proprement dit. clang peut s'avérer utile pour mettre en œuvre cette démarche - je crois savoir qu'il a été conçu avec des transformations de ce type à l'esprit. Bien entendu, vous devrez également apporter des modifications à la bibliothèque standard.

20voto

caf Points 114951

La norme stipule qu'une expression constante entière ayant la valeur 0, ou une telle expression convertie à l'unité de mesure void * est une constante à pointeur nul. Cela signifie que (void *)0 est toujours un pointeur nul, mais étant donné que int i = 0; , (void *)i n'a pas besoin de l'être.

L'implémentation C est constituée du compilateur et de ses en-têtes. Si vous modifiez les en-têtes pour redéfinir NULL mais ne modifie pas le compilateur pour corriger les initialisations statiques, alors vous avez créé une implémentation non conforme. C'est toute l'implémentation prise dans son ensemble qui a un comportement incorrect, et si vous l'avez cassée, vous n'avez vraiment personne d'autre à blâmer ;)

Vous devez fixer plus que de simples initialisations statiques, bien sûr - étant donné un pointeur p , if (p) est équivalent à if (p != NULL) en raison de la règle ci-dessus.

8voto

Doug T. Points 33360

Si vous utilisez la bibliothèque C std, vous rencontrerez des problèmes avec les fonctions qui peuvent retourner NULL. Par exemple, la fonction documentation sur malloc États :

Si la fonction n'a pas réussi à allouer le bloc de mémoire demandé, un pointeur est retourné.

Comme malloc et les fonctions connexes sont déjà compilées dans des binaires avec une valeur NULL spécifique, si vous redéfinissez NULL, vous ne pourrez pas utiliser directement la bibliothèque C std, à moins de reconstruire toute votre chaîne d'outils, y compris les librairies C std.

De plus, en raison de l'utilisation de NULL par la bibliothèque std, si vous redéfinissez NULL avant d'inclure les en-têtes std, vous risquez d'écraser une définition de NULL figurant dans les en-têtes. Tout ce qui est souligné serait incohérent dans les objets compilés.

Je préfèrerais définir votre propre NULL, "MYPRODUCT_NULL", pour vos propres usages et éviter ou traduire de/vers la bibliothèque C std.

6voto

Apalala Points 2999

Laissez NULL seul et traitez l'IO sur le port 0x0000 comme un cas spécial, peut-être en utilisant une routine écrite en assembleur, et donc non soumis à la sémantique standard du C. Autrement dit, ne redéfinissez pas NULL, redéfinissez le port 0x00000.

Notez que si vous écrivez ou modifiez un compilateur C, le travail nécessaire pour éviter de déréférencer NULL (en supposant que dans votre cas le CPU n'aide pas) est le même quelle que soit la façon dont NULL est défini, il est donc plus facile de laisser NULL défini comme zéro, et de s'assurer que zéro ne peut jamais être déréférencé depuis le C.

2voto

AProgrammer Points 31212

La configuration binaire du pointeur nul peut être différente de celle de l'entier 0. Mais l'expansion de la macro NULL doit être une constante du pointeur nul, c'est-à-dire un entier constant de valeur 0 qui peut être casté en (void*).

Pour obtenir le résultat que vous souhaitez tout en restant conforme, vous devrez modifier (ou peut-être configurer) votre chaîne d'outils, mais c'est réalisable.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X