Standard C nie wymaga, aby wskaźniki zerowe znajdowały się pod adresem zerowym maszyny. JEDNAK rzutowanie 0
stałej na wartość wskaźnika musi skutkować NULL
wskaźnikiem (§6.3.2.3 / 3), a obliczanie wskaźnika pustego jako wartości logicznej musi być fałszywe. To może być nieco niewygodne, jeśli naprawdę nie chcesz adres zerowy, a NULL
nie jest adresem zero.
Niemniej jednak, przy (ciężkich) modyfikacjach kompilatora i biblioteki standardowej, nie jest niemożliwe, aby NULL
być reprezentowanym za pomocą alternatywnego wzoru bitowego, przy jednoczesnym zachowaniu ścisłej zgodności z biblioteką standardową. To nie wystarczy po prostu zmienić definicję NULL
jednak sama, jak wtedy NULL
będzie oceniać true.
W szczególności musisz:
- Rozmieść dosłowne zera w przypisaniach do wskaźników (lub rzutach na wskaźniki), które mają zostać zamienione na inną magiczną wartość, taką jak
-1
.
- Rozmieść testy równości między wskaźnikami a stałą liczbą całkowitą,
0
aby zamiast tego sprawdzić magiczną wartość (§6.5.9 / 6)
- Rozmieść dla wszystkich kontekstów, w których typ wskaźnika jest oceniany jako wartość logiczna, aby sprawdzić równość z wartością magiczną zamiast sprawdzać zero. Wynika to z semantyki testowania równości, ale kompilator może wewnętrznie zaimplementować to inaczej. Patrz §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
- Jak wskazał caf, zaktualizuj semantykę inicjalizacji obiektów statycznych (§6.7.8 / 10) i częściowych inicjatorów złożonych (§6.7.8 / 21), aby odzwierciedlić nową reprezentację wskaźnika zerowego.
- Utwórz alternatywny sposób dostępu do prawdziwego adresu zero.
Jest kilka rzeczy, którymi nie musisz się zajmować. Na przykład:
int x = 0;
void *p = (void*)x;
Po tym p
NIE gwarantuje się, że wskaźnik będzie pusty. Obsługiwane są tylko stałe przypisania (jest to dobre podejście do dostępu do prawdziwego adresu zero). Również:
int x = 0;
assert(x == (void*)0); // CAN BE FALSE
Również:
void *p = NULL;
int x = (int)p;
x
nie jest gwarantowane 0
.
Krótko mówiąc, ten sam warunek był najwyraźniej rozważany przez komitet językowy C i rozważany dla tych, którzy wybraliby alternatywną reprezentację dla NULL. Wszystko, co musisz teraz zrobić, to wprowadzić duże zmiany w kompilatorze i hej, gotowe :)
Na marginesie, może być możliwe zaimplementowanie tych zmian na etapie transformacji kodu źródłowego przed właściwym kompilatorem. Oznacza to, że zamiast normalnego przepływu preprocesora -> kompilatora -> assemblera -> konsolidatora, można dodać preprocesor -> transformację NULL -> kompilator -> asembler -> linker. Następnie możesz wykonać transformacje, takie jak:
p = 0;
if (p) { ... }
/* becomes */
p = (void*)-1;
if ((void*)(p) != (void*)(-1)) { ... }
Wymagałoby to pełnego parsera C, a także parsera typów i analizy typów definicji typów i deklaracji zmiennych w celu określenia, które identyfikatory odpowiadają wskaźnikom. Jednak w ten sposób można uniknąć konieczności wprowadzania zmian w częściach generowania kodu przez kompilator. clang może być przydatny do realizacji tego - rozumiem, że został zaprojektowany z myślą o takich transformacjach. Oczywiście nadal prawdopodobnie będziesz musiał wprowadzić zmiany w bibliotece standardowej.
mprotect
zabezpieczyć. Lub, jeśli platforma nie ma ASLR lub podobnego, adresy poza fizyczną pamięcią platformy. Powodzenia.