Стандарт C не требует, чтобы нулевые указатели находились в нулевом адресе машины. ОДНАКО, приведение 0
константы к значению указателя должно приводить к получению NULL
указателя (§6.3.2.3 / 3), а оценка нулевого указателя как логического должна быть ложной. Это может быть немного неудобно , если вы действительно сделать хотите нулевой адрес, а NULL
не нулевой адрес.
Тем не менее, с (тяжелыми) модификациями компилятора и стандартной библиотеки, возможно, NULL
быть представлен альтернативным битовым шаблоном, оставаясь при этом строго совместимым со стандартной библиотекой. Это не достаточно просто изменить определение NULL
само по себе , однако, как и тогда NULL
будет вычисляться так.
В частности, вам необходимо:
- Сделайте так, чтобы буквальные нули в присваиваниях указателям (или приведения к указателям) преобразовывались в какое-либо другое магическое значение, например
-1
.
- Организуйте проверку равенства между указателями и постоянным целым числом,
0
чтобы вместо этого проверить магическое значение (§6.5.9 / 6)
- Организуйте для всех контекстов, в которых тип указателя оценивается как логическое, чтобы проверять равенство магическому значению вместо проверки нуля. Это следует из семантики проверки равенства, но компилятор может реализовать это по-другому внутри. См. §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
- Как указал caf, обновите семантику для инициализации статических объектов (§6.7.8 / 10) и частичных составных инициализаторов (§6.7.8 / 21), чтобы отразить новое представление нулевого указателя.
- Создайте альтернативный способ доступа к истинному нулевому адресу.
Есть некоторые вещи, с которыми вам не нужно заниматься. Например:
int x = 0;
void *p = (void*)x;
После этого p
НЕ гарантируется, что это будет нулевой указатель. Необходимо обрабатывать только постоянные присвоения (это хороший подход для доступа к истинному нулевому адресу). Точно так же:
int x = 0;
assert(x == (void*)0); // CAN BE FALSE
Также:
void *p = NULL;
int x = (int)p;
x
не гарантируется 0
.
Короче говоря, именно это условие, по-видимому, было рассмотрено комитетом по языку C, и были приняты во внимание те, кто выберет альтернативное представление для NULL. Все, что вам нужно сделать сейчас, это внести серьезные изменения в ваш компилятор, и готово :)
В качестве примечания: эти изменения можно реализовать с помощью этапа преобразования исходного кода до собственно компилятора. То есть, вместо обычного потока препроцессор -> компилятор -> ассемблер -> компоновщик, вы должны добавить препроцессор -> преобразование NULL -> компилятор -> ассемблер -> компоновщик. Затем вы можете делать такие преобразования, как:
p = 0;
if (p) { ... }
/* becomes */
p = (void*)-1;
if ((void*)(p) != (void*)(-1)) { ... }
Для этого потребуется полный синтаксический анализатор C, а также синтаксический анализатор типов и анализ определений типов и объявлений переменных, чтобы определить, какие идентификаторы соответствуют указателям. Однако, сделав это, вы можете избежать необходимости вносить изменения в части генерации кода собственно компилятора. clang может быть полезен для реализации этого - я понимаю, что он был разработан с учетом подобных преобразований. Конечно, вам все равно, вероятно, потребуется внести изменения в стандартную библиотеку.
mprotect
защитить. Или, если платформа не имеет ASLR и т.п., адреса за пределами физической памяти платформы. Удачи.