Ich schreibe C-Code für ein System, in dem die Adresse 0x0000 gültig ist und Port-E / A enthält. Daher bleiben mögliche Fehler, die auf einen NULL-Zeiger zugreifen, unentdeckt und verursachen gleichzeitig gefährliches Verhalten.
Aus diesem Grund möchte ich NULL neu definieren, um eine andere Adresse zu sein, zum Beispiel eine Adresse, die nicht gültig ist. Wenn ich versehentlich auf eine solche Adresse zugreife, erhalte ich einen Hardware-Interrupt, mit dem ich den Fehler behandeln kann. Ich habe zufällig Zugriff auf stddef.h für diesen Compiler, sodass ich den Standardheader tatsächlich ändern und NULL neu definieren kann.
Meine Frage ist: Wird dies im Widerspruch zum C-Standard stehen? Soweit ich aus 7.17 im Standard ersehen kann, ist das Makro implementierungsdefiniert. Gibt es irgendwo anders im Standard etwas, das besagt, dass NULL 0 sein muss ?
Ein weiteres Problem besteht darin, dass viele Compiler eine statische Initialisierung durchführen, indem sie unabhängig vom Datentyp alles auf Null setzen. Obwohl der Standard besagt, dass der Compiler Ganzzahlen auf Null und Zeiger auf NULL setzen soll. Wenn ich NULL für meinen Compiler neu definieren würde, dann weiß ich, dass eine solche statische Initialisierung fehlschlagen wird. Könnte ich das als falsches Compilerverhalten betrachten, obwohl ich die Compiler-Header manuell manuell geändert habe? Weil ich mit Sicherheit weiß, dass dieser bestimmte Compiler bei der statischen Initialisierung nicht auf das NULL-Makro zugreift.
mprotect
sichern können. Wenn die Plattform keine ASLR oder ähnliches hat, Adressen außerhalb des physischen Speichers der Plattform. Viel Glück.if(ptr) { /* do something on ptr*/ }
? Funktioniert es, wenn NULL anders als 0x0 definiert ist?Antworten:
Der C-Standard verlangt nicht, dass Nullzeiger an der Adresse Null der Maschine sind. Das Umwandeln einer
0
Konstante in einen Zeigerwert muss jedoch zu einemNULL
Zeiger führen (§6.3.2.3 / 3), und die Auswertung des Nullzeigers als Boolescher Wert muss falsch sein. Dies kann etwas umständlich sein, wenn Sie wirklich eine Nulladresse möchten undNULL
nicht die Nulladresse ist.Trotzdem ist es bei (starken) Änderungen am Compiler und an der Standardbibliothek nicht unmöglich,
NULL
mit einem alternativen Bitmuster dargestellt zu werden, während die Standardbibliothek strikt eingehalten wird. Es reicht jedoch nicht aus, einfach die Definition von sichNULL
selbst zu ändern , da dies dann alsNULL
wahr ausgewertet würde.Insbesondere müssten Sie:
-1
.0
, um stattdessen nach dem magischen Wert zu suchen (§6.5.9 / 6).Es gibt einige Dinge, die Sie nicht erledigen müssen. Beispielsweise:
Danach
p
ist NICHT garantiert, dass es sich um einen Nullzeiger handelt. Es müssen nur konstante Zuweisungen verarbeitet werden (dies ist ein guter Ansatz für den Zugriff auf die wahre Adresse Null). Gleichfalls:Ebenfalls:
x
ist nicht garantiert zu sein0
.Kurz gesagt, genau diese Bedingung wurde anscheinend vom C-Sprachkomitee berücksichtigt und Überlegungen für diejenigen angestellt, die eine alternative Vertretung für NULL wählen würden. Alles, was Sie jetzt tun müssen, ist, größere Änderungen an Ihrem Compiler vorzunehmen, und hey Presto, Sie sind fertig :)
Als Randnotiz kann es möglich sein, diese Änderungen mit einer Quellcode-Transformationsstufe vor dem eigentlichen Compiler zu implementieren. Das heißt, anstelle des normalen Ablaufs von Präprozessor -> Compiler -> Assembler -> Linker würden Sie einen Präprozessor -> NULL-Transformation -> Compiler -> Assembler -> Linker hinzufügen. Dann könnten Sie Transformationen durchführen wie:
Dies würde einen vollständigen C-Parser sowie einen Typparser und eine Analyse von Typedefs und Variablendeklarationen erfordern, um zu bestimmen, welche Bezeichner Zeigern entsprechen. Auf diese Weise können Sie jedoch vermeiden, dass Sie Änderungen an den Codegenerierungsteilen des Compilers vornehmen müssen. Clang kann nützlich sein, um dies zu implementieren - ich verstehe, dass es mit Blick auf solche Transformationen entwickelt wurde. Natürlich müssten Sie wahrscheinlich auch noch Änderungen an der Standardbibliothek vornehmen.
quelle
NULL
.Der Standard besagt, dass ein ganzzahliger Konstantenausdruck mit dem Wert 0 oder ein solcher in den
void *
Typ konvertierter Ausdruck eine Nullzeigerkonstante ist. Dies bedeutet , dass(void *)0
immer ein Nullzeiger ist, aber wenn man bedenktint i = 0;
,(void *)i
muss nicht sein.Die C-Implementierung besteht aus dem Compiler zusammen mit seinen Headern. Wenn Sie die Header so ändern, dass sie neu definiert werden
NULL
, den Compiler jedoch nicht so ändern, dass statische Initialisierungen korrigiert werden, haben Sie eine nicht konforme Implementierung erstellt. Es ist die gesamte Implementierung zusammengenommen, die ein falsches Verhalten aufweist, und wenn Sie es gebrochen haben, haben Sie wirklich niemanden zu beschuldigen;)Sie müssen mehr als nur statische Initialisierungen beheben, natürlich - einen Zeiger gegeben
p
,if (p)
äquivalent zuif (p != NULL)
aufgrund der obigen Regel.quelle
Wenn Sie die C std-Bibliothek verwenden, treten Probleme mit Funktionen auf, die NULL zurückgeben können. In der malloc-Dokumentation heißt es beispielsweise:
Da malloc und verwandte Funktionen bereits in Binärdateien mit einem bestimmten NULL-Wert kompiliert sind, können Sie die C std-Bibliothek nur dann direkt verwenden, wenn Sie Ihre gesamte Toolkette einschließlich der C std-Bibliotheken neu erstellen können.
Auch aufgrund der Verwendung von NULL durch die Standardbibliothek können Sie eine in den Kopfzeilen aufgeführte NULL-Definition überschreiben, wenn Sie NULL neu definieren, bevor Sie Standard-Header einschließen. Alles, was eingefügt wird, ist inkonsistent mit den kompilierten Objekten.
Ich würde stattdessen Ihren eigenen NULL-Wert "MYPRODUCT_NULL" für Ihre eigenen Zwecke definieren und entweder vermeiden oder von / in die C-Standardbibliothek übersetzen.
quelle
Lassen Sie NULL in Ruhe und behandeln Sie IO für Port 0x0000 als Sonderfall. Verwenden Sie möglicherweise eine in Assembler geschriebene Routine, die nicht der Standard-C-Semantik unterliegt. IOW, definieren Sie NULL nicht neu, definieren Sie Port 0x00000 neu.
Beachten Sie, dass beim Schreiben oder Ändern eines C-Compilers die Arbeit, die erforderlich ist, um eine Dereferenzierung von NULL zu vermeiden (vorausgesetzt, in Ihrem Fall hilft die CPU nicht), unabhängig von der Definition von NULL gleich ist. Daher ist es einfacher, NULL definiert zu lassen als Null, und stellen Sie sicher, dass Null niemals von C dereferenziert werden kann.
quelle
*p
,p[]
oderp()
, so dass nur der Compiler über diejenigen zu kümmern braucht 0x0000 IO - Port zu schützen.In Anbetracht der extremen Schwierigkeit, NULL neu zu definieren, wie von anderen erwähnt, ist es möglicherweise einfacher, die Dereferenzierung für bekannte Hardwareadressen neu zu definieren . Fügen Sie beim Erstellen einer Adresse 1 zu jeder bekannten Adresse hinzu, sodass Ihr bekannter E / A-Port wie folgt lautet:
Wenn die Adressen, mit denen Sie sich befassen, zusammengefasst sind und Sie sich sicher fühlen können, dass das Hinzufügen von 1 zur Adresse nicht mit irgendetwas in Konflikt steht (was in den meisten Fällen nicht der Fall sein sollte), können Sie dies möglicherweise sicher tun. Und dann müssen Sie sich keine Gedanken mehr über die Neuerstellung Ihrer Toolkette / Standardbibliothek und Ausdrücke in der folgenden Form machen:
funktioniert noch immer
Verrückt, ich weiß, aber ich dachte nur, ich würde die Idee da rauswerfen :).
quelle
Das Bitmuster für den Nullzeiger ist möglicherweise nicht dasselbe wie das Bitmuster für die Ganzzahl 0. Die Erweiterung des NULL-Makros muss jedoch eine Nullzeigerkonstante sein, dh eine konstante Ganzzahl mit dem Wert 0, die in (void) umgewandelt werden kann *).
Um das gewünschte Ergebnis zu erzielen und gleichzeitig konform zu bleiben, müssen Sie Ihre Werkzeugkette ändern (oder möglicherweise konfigurieren), dies ist jedoch erreichbar.
quelle
Du fragst nach Ärger. Durch eine Neudefinition
NULL
auf einen Wert ungleich Null wird dieser Code beschädigt:quelle