Я пишу код C для системы, где адрес 0x0000 действителен и содержит ввод-вывод порта. Следовательно, любые возможные ошибки, которые обращаются к указателю NULL, останутся необнаруженными и в то же время вызовут опасное поведение.
По этой причине я хочу переопределить NULL, чтобы он был другим адресом, например, адресом, который недействителен. Если я случайно получу доступ к такому адресу, я получу аппаратное прерывание, где я смогу обработать ошибку. У меня есть доступ к stddef.h для этого компилятора, поэтому я могу изменить стандартный заголовок и переопределить NULL.
Мой вопрос: будет ли это противоречить стандарту C? Насколько я могу судить из стандарта 7.17, макрос определяется реализацией. Есть ли что-нибудь еще в стандарте, утверждающее, что NULL должен быть 0?
Другая проблема заключается в том, что многие компиляторы выполняют статическую инициализацию, устанавливая все на ноль, независимо от типа данных. Хотя в стандарте сказано, что компилятор должен устанавливать целые числа в ноль, а указатели - в NULL. Если бы я переопределил NULL для своего компилятора, я знал, что такая статическая инициализация не удастся. Могу ли я считать это неправильным поведением компилятора, хотя я смело изменял заголовки компилятора вручную? Потому что я точно знаю, что этот конкретный компилятор не обращается к макросу NULL при статической инициализации.
mprotect
защитить. Или, если платформа не имеет ASLR и т.п., адреса за пределами физической памяти платформы. Удачи.if(ptr) { /* do something on ptr*/ }
? Будет ли это работать, если NULL определен отличным от 0x0?Ответы:
Стандарт C не требует, чтобы нулевые указатели находились в нулевом адресе машины. ОДНАКО, приведение
0
константы к значению указателя должно приводить к получениюNULL
указателя (§6.3.2.3 / 3), а оценка нулевого указателя как логического должна быть ложной. Это может быть немного неудобно , если вы действительно сделать хотите нулевой адрес, аNULL
не нулевой адрес.Тем не менее, с (тяжелыми) модификациями компилятора и стандартной библиотеки, возможно,
NULL
быть представлен альтернативным битовым шаблоном, оставаясь при этом строго совместимым со стандартной библиотекой. Это не достаточно просто изменить определениеNULL
само по себе , однако, как и тогдаNULL
будет вычисляться так.В частности, вам необходимо:
-1
.0
чтобы вместо этого проверить магическое значение (§6.5.9 / 6)Есть некоторые вещи, с которыми вам не нужно заниматься. Например:
После этого
p
НЕ гарантируется, что это будет нулевой указатель. Необходимо обрабатывать только постоянные присвоения (это хороший подход для доступа к истинному нулевому адресу). Точно так же:Также:
x
не гарантируется0
.Короче говоря, именно это условие, по-видимому, было рассмотрено комитетом по языку C, и были приняты во внимание те, кто выберет альтернативное представление для NULL. Все, что вам нужно сделать сейчас, это внести серьезные изменения в ваш компилятор, и готово :)
В качестве примечания: эти изменения можно реализовать с помощью этапа преобразования исходного кода до собственно компилятора. То есть, вместо обычного потока препроцессор -> компилятор -> ассемблер -> компоновщик, вы должны добавить препроцессор -> преобразование NULL -> компилятор -> ассемблер -> компоновщик. Затем вы можете делать такие преобразования, как:
Для этого потребуется полный синтаксический анализатор C, а также синтаксический анализатор типов и анализ определений типов и объявлений переменных, чтобы определить, какие идентификаторы соответствуют указателям. Однако, сделав это, вы можете избежать необходимости вносить изменения в части генерации кода собственно компилятора. clang может быть полезен для реализации этого - я понимаю, что он был разработан с учетом подобных преобразований. Конечно, вам все равно, вероятно, потребуется внести изменения в стандартную библиотеку.
источник
NULL
.Стандарт утверждает, что целочисленное постоянное выражение со значением 0 или такое выражение, преобразованное в
void *
тип, является константой нулевого указателя. Это означает , что(void *)0
всегда является указателем NULL, но данныеint i = 0;
,(void *)i
не нужно быть.Реализация C состоит из компилятора и его заголовков. Если вы измените заголовки для переопределения
NULL
, но не измените компилятор для исправления статических инициализаций, значит, вы создали несоответствующую реализацию. Это вся реализация, взятая вместе, имеет некорректное поведение, и если вы ее сломали, вам действительно некого винить;)Вы должны исправить больше , чем просто статические инициализацые, конечно , - данный указатель
p
,if (p)
эквивалентноif (p != NULL)
, в связи с вышеприведенным правилом.источник
Если вы используете библиотеку C std, вы столкнетесь с проблемами с функциями, которые могут возвращать NULL. Например, в документации malloc указано:
Поскольку malloc и связанные с ним функции уже скомпилированы в двоичные файлы с определенным значением NULL, если вы переопределите NULL, вы не сможете напрямую использовать библиотеку C std, если вы не сможете перестроить всю цепочку инструментов, включая библиотеки C std.
Также из-за того, что библиотека std использует NULL, если вы переопределите NULL перед включением заголовков std, вы можете перезаписать определение NULL, указанное в заголовках. Все, что встроено, будет несовместимо с скомпилированными объектами.
Вместо этого я бы определил ваш собственный NULL, «MYPRODUCT_NULL», для вашего собственного использования и либо избегал, либо переводил из / в библиотеку C std.
источник
Оставьте NULL в покое и относитесь к вводу-выводу на порт 0x0000 как к особому случаю, возможно, используя подпрограмму, написанную на ассемблере и, следовательно, не подчиняющуюся стандартной семантике C. IOW, не переопределяйте NULL, переопределяйте порт 0x00000.
Обратите внимание, что если вы пишете или изменяете компилятор C, работа, необходимая для предотвращения разыменования NULL (при условии, что в вашем случае ЦП не помогает), одинакова, независимо от того, как определяется NULL, поэтому проще оставить NULL определенным как ноль, и убедитесь, что ноль нельзя разыменовать из C.
источник
*p
,p[]
илиp()
, поэтому компилятору нужно заботиться только о них, чтобы защитить порт ввода-вывода 0x0000.Учитывая крайнюю сложность переопределения NULL, как упоминалось другими, возможно, проще переопределить разыменование для хорошо известных аппаратных адресов. При создании адреса добавьте 1 к каждому известному адресу, чтобы ваш известный порт ввода-вывода был:
Если адреса, которые вас интересуют, сгруппированы вместе, и вы можете быть уверены, что добавление 1 к адресу ни с чем не будет конфликтовать (чего не должно быть в большинстве случаев), вы можете сделать это безопасно. И тогда вам не нужно беспокоиться о восстановлении цепочки инструментов / std lib и выражений в форме:
все еще работают
Безумно знаю, но просто подумал, что выкину эту идею там :).
источник
Битовый шаблон для нулевого указателя может не совпадать с битовым шаблоном для целого числа 0. Но раскрытие макроса NULL должно быть константой нулевого указателя, то есть постоянным целым числом значения 0, которое может быть преобразовано в (void *).
Чтобы достичь желаемого результата, оставаясь при этом согласованным, вам придется изменить (или, возможно, настроить) цепочку инструментов, но это достижимо.
источник
Вы просите неприятностей. Переопределение
NULL
на ненулевое значение нарушит этот код:источник