Проблема с указателями NULL на платформе Гарвардской архитектуры
Интересная проблема, с которой мы столкнулись на этой неделе.
Мы работаем в C на встроенной платформе Гарвардской архитектуры, которая имеет 16-битные адреса данных и 32-битные адреса кода.
Эта проблема возникает, когда вы работаете с указателями функций. Если у вас есть код как
if (fp) fp();
или же
if (fp != 0) fp();
Все отлично.
Однако, если у вас есть код вроде
if (fp != NULL) fp();
тогда, потому что NULL
определяется как (void *) 0
компилятор (в данном случае gcc) а) не предупреждает и б) делает 16-битное сравнение с указателем вашей функции вместо 32-битного сравнения. Хорошо, если ваш указатель на функцию не лежит на границе 64 КБ, поэтому все младшие 16 битов равны 0.
На данный момент у нас есть большие фрагменты кода, которые содержат явные проверки на NULL. Большинство из них будут указателями данных, но некоторые из них будут указателями функций. Быстрый grep для != NULL
или же == NULL
выявил более 3000 результатов, многим нужно пройти вручную, чтобы проверить.
Итак, то, что мы хотели бы сейчас было бы
способ найти все случаи, когда сравниваются указатели функций (но не указатели данных) (поэтому вместо этого мы можем сравнить их с FP_NULL, который мы определили бы как 32-битный 0), или
переопределить NULL таким образом, чтобы он делал правильные вещи.
(Или, я полагаю, обновить наш порт gcc, чтобы обнаружить и правильно обработать этот случай).
Я не могу придумать ни одного подхода, который работает для 1. Единственный подход, который я могу придумать для 2, - это переопределить NULL как указатель на функцию 0, что было бы очень расточительным для подавляющего большинства сравнений с указателями данных. (32-битное сравнение - это 4 инструкции, 16-битное сравнение - 1 инструкция).
Есть мысли или предложения?
6 ответов
Мне кажется, самый простой способ - заменить все случаи NULL
от 0
, Это работает для указателя на функцию (как вы говорите) и указателя на объект.
Это вариант (2) переопределить NULL для простого 0
,
Но тот факт, что вы не можете сравнить указатели функций с NULL
это ошибка в вашей реализации. C99 утверждает, что сравнение константы нулевого указателя возможно как с объектными, так и с функциональными указателями, и что значение NULL должно расширяться до этой константы.
Небольшое дополнение из вопроса 5.8 C-FAQ:
Q: Допустимо ли значение NULL для указателей на функции?
A: Да (но см. Вопрос 4.13)
Смешивание указателей на функции с (void *) 0
(Ответ на комментарий R..). Я считаю, что с помощью указателей функций и (void *) 0
вместе это четко определено. В своих рассуждениях я буду ссылаться на разделы проекта C56 1299 года, но не буду цитировать большие части, чтобы сделать его читабельным. Это также должно быть применимо к C89.
- 6.3.2.3 (3) определяет целочисленное константное выражение
0
и такое выражение приведено к(void *)
как константа нулевого указателя. И: "Если константа нулевого указателя преобразуется в тип указателя, результирующий указатель, называемый нулевым указателем, гарантированно сравнивается с неравным указателем на любой объект или функцию". - 6.8.9 определяет
==
а также!=
операнды для (среди прочего) операнда указателя и константы нулевого указателя. Для них: "Если один операнд является указателем, а другой - константой нулевого указателя, константа нулевого указателя преобразуется в тип указателя".
Вывод: в fp == (void *) 0
константа нулевого указателя преобразуется в тип fp
, Этот нулевой указатель можно сравнить с fp
и гарантированно будет неравным fp
если это указывает на функцию. Назначение (=
) имеет аналогичное положение, поэтому fp = (void *) 0;
также четко определен C.
То, как вы описываете, должно работать:
6.3.2.3/3 Целочисленное константное выражение со значением 0 или такое выражение, приведенное к типу void *, называется константой нулевого указателя. Если константа нулевого указателя преобразуется в тип указателя, результирующий указатель, называемый нулевым указателем, гарантированно сравнивается с неравным указателем на любой объект или функцию.
Таким образом, либо NULL был переопределен на что-то не 0
или же (void*)0
(или эквивалент?) или ваш компилятор не соответствует.
Попробуйте переопределить NULL самостоятельно 0
) ведь #include во всех файлах:-)
просто ради удовольствия: попробуй gcc -E
(для вывода предварительно обработанного источника) в проблемном файле и проверьте расширение NULL
Вы можете попробовать это:
#ifdef NULL
#undef NULL
#endif
#define NULL 0
Вы можете попробовать взломать сегмент (на самом деле только взломать), чтобы вы могли использовать быстрое 16-битное сравнение без какого-либо риска. Создайте сегменты на каждой границе n*0x10000 размером 4 (или даже меньше), чтобы никогда не существовало реальной функции.
Это зависит от объема памяти встроенного устройства, является ли это хорошим или действительно плохим решением. Это может сработать, если у вас есть 1 МБ обычной Flash, которая никогда не изменится. Это будет больно, если у вас 64 МБ Nand Flash.
Вот несколько предложений:
временно изменить NULL на
(char*)0
или что-то еще, что неявно преобразуется в указатель на функцию. Это должно дать предупреждение о каждом сравнении с несоответствующим указателем. Затем вы можете запустить полученный вывод компилятора через такой инструмент, как grep, и найти типичный шаблон для указателей на функции, например(*)(
,переопределить NULL как
0
(без актерского состава *). Это еще одно правильное определение для NULL и может сделать правильную вещь для вас, но без гарантий.
Отредактируйте системные заголовки реализации, чтобы заменить все вхождения
#define NULL ((void *)0)
с
#define NULL 0
Затем отправьте отчет об ошибке поставщику. Вам не нужно изменять ваш (совершенно правильный, хотя и некрасивый стиль) код из-за ошибки в компиляторе поставщика.