Является ли это прямым объявлением указателя функции, действительным в C?

Я пытаюсь выяснить, имеет ли в ANSI-C следующее утверждение:

Первый файл:

extern void * fptr;   // opaque forward declaration.
int main (void) {
  fptr = NULL;        // set the function pointer to NULL
}

Второй файл:

typedef int (*fptr_t)(int);
fptr_t fptr;         // real declaration of the function pointer

Для меня это должно быть недействительным, поскольку fptr, если объявлено двумя разными типами, но ни gcc, ни clang не дает никаких предупреждений.

Я бы более конкретно интересовался точными точками стандарта C11, которые позволяют заключить, почему он действителен (или недействителен).


EDIT: в стандарте C11, 6.2.7: 2 говорит:

Все объявления, относящиеся к одному объекту или функции, должны иметь совместимый тип; в противном случае поведение undefined.

Но я не могу найти, как решить, совместим ли void* с fptr_t.

Ответ 1

C99:

6.2.7 Совместимый тип и составной тип

пункт 2:

Все объявления, относящиеся к одному и тому же объекту или функции, должны иметь совместимый тип; в противном случае поведение undefined.

6.7.5.1 Деклараторы указателей

пункт 2:

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

Без дальнейшего поиска в стандарте легко видеть, что void и функция не являются совместимыми типами.

Я готов поспорить, что это не изменится на C11. C неявно поддерживал отдельные пространства кода и данных и различные размеры и представления указателей кода и данных в течение длительного времени, и было бы странно удалять эту функцию и ограничивать язык меньшим подмножеством машин, для которых они будут доступны. Итак, снимите с осторожностью. Лучше с доказательством.

Ответ 2

Нет, это неверно, потому что вы сохраняете обычный указатель (NULL, void*) в ячейку памяти, которая на самом деле является указателем на функцию. Вы просто скрываете это от компилятора, а компоновщику все равно, но в конце дня у вас есть поведение undefined, потому что два типа указателя не обязательно совместимы. Конечно, это может работать на многих системах, но, возможно, не на всех.

Подробнее о указателях функций и указателях void см. здесь: может быть void * использоваться для хранения указателей функций? - в то время как это немного другой случай, чем то, что вы "Представляя ответы, ответы по-прежнему актуальны.

Ответ 3

Он должен работать, но он недействителен. В первом файле вы объявляете, что идентификатор fptr будет определен в другом модуле компиляции и что он будет void *. Во втором файле вы определяете идентификатор, но теперь он является указателем на функцию. Скомпилированные файлы обычно не сохраняют тип объектов (только адрес) так:

  • компилятор не знает, что другой источник объявляет другой тип и не может выпустить предупреждение
  • линкер не требуется для управления этим, поэтому стандартное требование не требуется, обычная реализация не контролирует типы, поэтому предупреждение не будет

В обычных реализациях все указатели имеют одинаковое представление, а преобразование указателя на функцию в указатель на void не изменяет представление, поэтому сглаживание даст ожидаемые результаты.

Но это по-прежнему поведение undefined (*) для каждого стандарта, потому что 6.2.5 Типы § 27 объявляют (подчеркивают мои):

Указатель на пустоту должен иметь те же требования к представлению и выравниванию, что и указатель на тип символа .39) Аналогично, указатели на квалифицированные или неквалифицированные версии совместимые типы должны иметь одинаковые требования к представлению и выравниванию. Все указатели на типы структуры должны иметь одинаковые требования к представлению и выравниванию как друг друга. Все указатели на типы профсоюзов должны иметь одинаковое представление и согласования требований друг с другом. Указатели на другие типы не обязательно должны иметь одинаковые требования представления или выравнивания.

(*), поскольку он не проверен обычной реализацией, существует небольшой риск того, что UB будет обнаружен и оптимизирован текущими версиями компиляторов. Но я бы никогда не делал этого в производственном коде...

Ответ 4

void * - это указатель на тип объекта, который отличается от указателя типа функции. Они не совместимы.

Но:

Portabilitiy выдает J.5.7. Функция указателя бросает

  • Указатель на объект или на void может быть нажата на указатель на функцию, позволяя использовать данные в качестве функция (6.5.4).

  • Указатель на функцию может быть переведен в указатель к объекту или к пустоте, позволяя проверять функцию или измененный (например, отладчиком) (6.5.4).

Тогда почему бы не полностью скрыть указатель в вашем модуле и экрлизировать функции, чтобы манипулировать им? Это позволит избежать проблемы с псевдонимом.