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