Стандарт C не требует, чтобы нулевые указатели были по нулевому адресу машины. ОДНАКО приведение константы 0
к значению указателя должно привести к указателю NULL
(§6.3.2.3 / 3), а оценка нулевого указателя как логического значения должна быть ложной. Это может быть немного неловко, если вы действительно делаете хотите нулевой адрес, а NULL
не является нулевым адресом.
Тем не менее, с (тяжелыми) модификациями компилятора и стандартной библиотеки, не исключено, что NULL
будет представлен альтернативным битовым шаблоном, оставаясь при этом строго совместимым со стандартной библиотекой. Однако не достаточно, чтобы просто изменить определение самого NULL
, так как тогда NULL
будет иметь значение true.
В частности, вам необходимо:
- Организовать буквальные нули в назначениях указателей (или приведений к указателям) для преобразования в какую-то другую магическую ценность, такую как
-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
.
Короче говоря, это самое условие, по-видимому, рассматривалось комитетом по языку Си, и учитывались те, кто выбрал бы альтернативное представление для NULL. Все, что вам нужно сделать сейчас, это внести серьезные изменения в ваш компилятор, и, эй, готово:)
В качестве примечания, возможно, что эти изменения будут реализованы на этапе преобразования исходного кода до того, как компилятор будет создан. То есть вместо обычного потока препроцессор -> компилятор -> ассемблер -> компоновщик вы бы добавили препроцессор -> преобразование NULL -> компилятор -> ассемблер -> компоновщик. Тогда вы могли бы сделать преобразования как:
p = 0;
if (p) { ... }
/* becomes */
p = (void*)-1;
if ((void*)(p) != (void*)(-1)) { ... }
Для этого потребуется полный синтаксический анализатор C, а также синтаксический анализатор типов и анализ определений типов и переменных, чтобы определить, какие идентификаторы соответствуют указателям. Однако, делая это, вы можете избежать внесения изменений в части генерации кода собственно компилятора. clang может быть полезен для реализации этого - я понимаю, что он был разработан с учетом подобных преобразований. Конечно, вам, вероятно, все равно придется внести изменения в стандартную библиотеку.