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 einem NULL
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 und NULL
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 sich NULL
selbst zu ändern , da dies dann als NULL
wahr ausgewertet würde.
Insbesondere müssten Sie:
- Sorgen Sie dafür, dass wörtliche Nullen in Zuweisungen zu Zeigern (oder Umwandlungen zu Zeigern) in einen anderen magischen Wert umgewandelt werden, z
-1
.
- Vereinbaren Sie Gleichheitstests zwischen Zeigern und einer konstanten Ganzzahl
0
, um stattdessen nach dem magischen Wert zu suchen (§6.5.9 / 6).
- Ordnen Sie alle Kontexte an, in denen ein Zeigertyp als Boolescher Wert ausgewertet wird, um die Gleichheit mit dem magischen Wert zu überprüfen, anstatt nach Null zu suchen. Dies folgt aus der Semantik der Gleichheitstests, aber der Compiler kann sie intern anders implementieren. Siehe §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
- Aktualisieren Sie, wie im Café erwähnt, die Semantik für die Initialisierung statischer Objekte (§6.7.8 / 10) und partieller zusammengesetzter Initialisierer (§6.7.8 / 21), um die neue Nullzeigerdarstellung widerzuspiegeln.
- Erstellen Sie eine alternative Möglichkeit, auf die wahre Adresse Null zuzugreifen.
Es gibt einige Dinge, die Sie nicht erledigen müssen. Beispielsweise:
int x = 0;
void *p = (void*)x;
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:
int x = 0;
assert(x == (void*)0); // CAN BE FALSE
Ebenfalls:
void *p = NULL;
int x = (int)p;
x
ist nicht garantiert zu sein 0
.
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:
p = 0;
if (p) { ... }
/* becomes */
p = (void*)-1;
if ((void*)(p) != (void*)(-1)) { ... }
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.
mprotect
sichern können. Wenn die Plattform keine ASLR oder ähnliches hat, Adressen außerhalb des physischen Speichers der Plattform. Viel Glück.