J'écris du code C pour un système où l'adresse 0x0000 est valide et contient des E / S de port. Par conséquent, tous les bogues possibles qui accèdent à un pointeur NULL resteront non détectés et en même temps provoqueront un comportement dangereux.
Pour cette raison, je souhaite redéfinir NULL pour être 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 gérer l'erreur. Il se trouve que j'ai accès à stddef.h pour ce compilateur, donc je peux en fait modifier l'en-tête standard et redéfinir NULL.
Ma question est la suivante: cela sera-t-il en conflit avec la norme C? Pour autant que je sache à partir de 7.17 dans la norme, la macro est définie par l'implémentation. Y a-t-il quelque chose ailleurs dans la norme indiquant que NULL doit être 0?
Un autre problème est que de nombreux compilateurs effectuent une initialisation statique en définissant tout sur zéro, quel que soit le type de données. Même si la norme stipule que le compilateur doit définir les entiers sur zéro et les pointeurs sur NULL. Si je redéfini NULL pour mon compilateur, je sais qu'une telle initialisation statique échouera. Pourrais-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.
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.if(ptr) { /* do something on ptr*/ }
? Cela fonctionnera-t-il si NULL est défini différemment de 0x0?Réponses:
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 unNULL
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, etNULL
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 deNULL
lui-même, comme celaNULL
serait alors vrai.Plus précisément, vous devrez:
-1
.0
pour vérifier la valeur magique à la place (§6.5.9 / 6)Il y a certaines choses que vous n'avez pas à gérer. Par exemple:
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:Aussi:
x
n'est pas garanti0
.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:
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.
la source
NULL
.La norme stipule qu'une expression constante entière avec la valeur 0, ou une telle expression convertie en
void *
type, est une constante de pointeur nul. Cela signifie qu'il(void *)0
s'agit toujours d'un pointeur nul, mais donnéint i = 0;
,(void *)i
n'a pas besoin de l'être.L'implémentation C se compose du compilateur avec ses en-têtes. Si vous modifiez les en-têtes pour redéfinir
NULL
, mais ne modifiez pas le compilateur pour corriger les initialisations statiques, vous avez créé une implémentation non conforme. C'est toute l'implémentation prise ensemble qui a un comportement incorrect, et si vous l'avez cassée, vous n'avez vraiment personne d'autre à blâmer;)Vous devez corriger plus que de simples initialisations statiques, bien sûr - étant donné un pointeur
p
,if (p)
équivaut àif (p != NULL)
, en raison de la règle ci-dessus.la source
Si vous utilisez la bibliothèque C std, vous rencontrerez des problèmes avec les fonctions qui peuvent renvoyer NULL. Par exemple, la documentation malloc indique:
Comme malloc et les fonctions associées sont déjà compilés en binaires avec une valeur NULL spécifique, si vous redéfinissez NULL, vous ne pourrez pas utiliser directement la bibliothèque C std à moins que vous ne puissiez reconstruire toute votre chaîne d'outils, y compris les bibliothèques 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 pouvez remplacer une définition NULL répertoriée dans les en-têtes. Tout élément inséré serait incompatible avec les objets compilés.
Je définirais plutôt votre propre NULL, "MYPRODUCT_NULL", pour vos propres utilisations et éviterais ou traduirais de / vers la bibliothèque C std.
la source
Laissez NULL seul et traitez 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 C standard. IOW, ne redéfinissez pas NULL, redéfinissez le port 0x00000.
Notez que si vous écrivez ou modifiez un compilateur C, le travail requis 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 définition de NULL, il est donc plus facile de laisser NULL défini comme zéro, et assurez-vous que zéro ne peut jamais être déréférencé de C.
la source
*p
,p[]
oup()
, donc le compilateur n'a besoin de se soucier que de ceux qui protègent le port IO 0x0000.Compte tenu de l'extrême difficulté de redéfinir NULL comme mentionné par d'autres, il est peut-être plus facile de redéfinir le déréférencement pour les adresses matérielles bien connues. Lors de la création d'une adresse, ajoutez 1 à chaque adresse connue, de sorte que votre port IO connu soit:
Si les adresses qui vous concernent sont regroupées et que vous pouvez être sûr que l'ajout de 1 à l'adresse n'entrera en conflit avec rien (ce qui ne devrait pas dans la plupart des cas), vous pourrez peut-être le faire en toute sécurité. Et puis vous n'avez pas à vous soucier de la reconstruction de votre chaîne d'outils / bibliothèque std et des expressions sous la forme:
travaille toujours
Fou je sais, mais je pensais juste que je lancerais l'idée là-bas :).
la source
Le modèle de bits pour le pointeur nul peut ne pas être le même que le modèle de bits pour l'entier 0. Mais le développement de la macro NULL doit être une constante de pointeur nul, c'est-à-dire un entier constant de valeur 0 qui peut être converti en (void *).
Pour obtenir le résultat souhaité tout en restant conforme, vous devrez modifier (ou peut-être configurer) votre chaîne d'outils, mais c'est réalisable.
la source
Vous demandez des ennuis. La redéfinition
NULL
à une valeur non nulle cassera ce code:la source