Подтвердить что ты не робот

Проблема с указателями 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 инструкция).

Любые мысли или предложения?

4b9b3361

Ответ 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

Затем напишите отчет об ошибке поставщику. Вам не нужно изменять свой (совершенно правильный, хотя и уродливый) код из-за ошибки в компиляторе поставщика.