Проблема с указателями NULL на платформе Гарвардской архитектуры - PullRequest
17 голосов
/ 08 октября 2010

Интересная проблема, с которой мы столкнулись здесь на этой неделе.

Мы работаем в C на встроенной платформе Гарвардской архитектуры, которая имеет 16-битные адреса данных и 32-битные адреса кода.

Проблема возникает, когда вы работаете с указателями функций.Если у вас есть код типа

if (fp) fp();

или

if (fp != 0) fp();

, все в порядке.

Однако, если у вас есть код, подобный

if (fp != NULL) fp();

, то, поскольку NULL определен как (void *) 0, компилятор (в данном случае gcc) a) не предупреждает и b)выполняет 16-битное сравнение с указателем вашей функции вместо 32-битного сравнения.Хорошо, если ваш указатель на функцию не находится на границе 64 КБ, поэтому все 16 младших битов равны 0.

В настоящий момент у нас есть большие массивы кода, которые содержат явные проверки на NULL.Большинство из них будут указателями данных, но некоторые из них будут указателями функций.Быстрый grep для != NULL или == NULL показал более 3000 результатов, для многих, чтобы пройти вручную, чтобы проверить.

Итак, что мы хотели бы сейчас, это либо

  1. способ найти все случаи, когда сравниваются указатели функций (но не указатели данных) (так что мы можемвместо этого пусть они сравниваются с FP_NULL, который мы определили бы как 32-битный 0), или

  2. , чтобы переопределить NULL таким образом, чтобы он действовал правильно.

  3. (Или, я полагаю, обновить наш порт gcc для обнаружения и правильной обработки этого случая).

Я не могу вспомнитьлюбой подход, который работает для 1. Единственный подход, который я могу придумать для 2, состоит в том, чтобы переопределить NULL как указатель на функцию 0, что было бы очень расточительным для подавляющего большинства сравнений с указателями данных.(32-битное сравнение - это 4 инструкции, 16-битное сравнение - 1 инструкция).

Есть мысли или предложения?

Ответы [ 6 ]

18 голосов
/ 08 октября 2010

Мне кажется, самый простой способ - заменить все вхождения NULL на 0.Это работает для указателя на функцию (как вы говорите) и указателей на объект.

Это вариант (2) переопределения NULL для обычного 0.

Но тот факт, что вы не можете сравнить функциюуказатели с NULL - это ошибка в вашей реализации.C99 утверждает, что сравнение константы нулевого указателя возможно как с объектными, так и с функциональными указателями, и что NULL должен расширяться до этой константы.

Небольшое дополнение из вопроса C-FAQ 5.8 :

В: Допустимо ли значение 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.

3 голосов
/ 08 октября 2010

Вы можете попробовать взломать сегмент (на самом деле только взломать), чтобы вы могли использовать быстрое 16-битное сравнение без какого-либо риска.Создайте сегменты на каждой границе n * 0x10000 размером 4 (или даже меньше), чтобы никогда не существовало реальной функции.

Это зависит от объема памяти встроенного устройства, если это хорошо илидействительно плохое решение.Это может сработать, если у вас есть 1 МБ обычной Flash, которая никогда не изменится.Будет больно, если у вас 64 МБ Nand Flash.

3 голосов
/ 08 октября 2010

Способ, который вы описываете, должен работать:

6.3.2.3 / 3 Целочисленное константное выражение со значением 0 или такое выражение, приведенное к типу void *, называется константой нулевого указателя.Если константа нулевого указателя преобразуется в тип указателя, результирующий указатель, называемый нулевым указателем, гарантированно сравнивается с неравным указателем на любой объект или функцию.

Итак, либо NULL былпереопределено на что-то, отличное от 0 или (void*)0 (или эквивалентное?), или ваш компилятор не соответствует.

Попробуйте переопределить NULL самостоятельно (просто 0) после всех #include во всех файлах:-)

только для ударов: попробуйте gcc -E (для вывода предварительно обработанного источника) в файле проблемы и проверьте расширение NULL

3 голосов
/ 08 октября 2010

Вы можете попробовать это:

#ifdef NULL
  #undef NULL
#endif
#define NULL 0
2 голосов
/ 08 октября 2010

Вот несколько предложений:

  1. временно изменить NULL на (char*)0 или что-то еще, что неявно не преобразуется в указатель на функцию. Это должно дать предупреждение о каждом сравнении с несоответствующим указателем. Затем вы можете запустить полученный вывод компилятора через инструмент, подобный grep, и найти типичный шаблон для указателей на функции, такой как (*)(.

  2. переопределить NULL как 0 (без приведения к пустоте *). Это еще одно правильное определение для NULL, и оно может помочь вам, но без гарантий.

1 голос
/ 08 октября 2010

Редактировать системные заголовки реализации, чтобы заменить все вхождения

#define NULL ((void *)0)

с

#define NULL 0

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

...