Почему указатели функций и указатели данных несовместимы в C / C ++?

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

125

Решение

Архитектура не должна хранить код и данные в одной и той же памяти. Благодаря гарвардской архитектуре код и данные хранятся в совершенно другой памяти. Большинство архитектур являются архитектурами фон Неймана с кодом и данными в одной и той же памяти, но C не ограничивает себя только определенными типами архитектур, если это вообще возможно.

168

Другие решения

Некоторые компьютеры имеют (имели) отдельные адресные пространства для кода и данных. На таком оборудовании это просто не работает.

Язык предназначен не только для современных настольных приложений, но и для того, чтобы его можно было реализовать на большом наборе оборудования.


Похоже, комитет по языку C никогда не собирался void* чтобы быть указателем на функцию, они просто хотели общий указатель на объекты.

Обоснование C99 говорит:

6.3.2.3. Указатели
В настоящее время C реализован на широком спектре архитектур. Хотя некоторые из них
в архитектурах используются одинаковые указатели, максимально соответствующие размеру целочисленного типа
Переносимый код не может предполагать какого-либо необходимого соответствия между различными типами указателей и целочисленными типами. В некоторых реализациях указатели могут быть даже шире, чем любой целочисленный тип.

Использование void* (Указатель на void») Как типовой указатель на объект является изобретением комитета C89. Принятие этого типа стимулировалось желанием указать аргументы прототипа функции, которые либо тихо преобразуют произвольные указатели (как в fread) или жаловаться, если тип аргумента не совсем совпадает (как в strcmp). Ничего не сказано об указателях на функции, которые могут быть несопоставимы с указателями на объекты и / или целыми числами.

Заметка Ничего не сказано об указателях на функции в последнем абзаце. Они могут отличаться от других указателей, и комитет об этом знает.

37

Для тех, кто помнит MS-DOS, Windows 3.1 и старше, ответ довольно прост. Все они использовали для поддержки нескольких различных моделей памяти с различными комбинациями характеристик для кода и указателей данных.

Например, для модели Compact (маленький код, большие данные):

sizeof(void *) > sizeof(void(*)())

и наоборот в модели Medium (большой код, маленькие данные):

sizeof(void *) < sizeof(void(*)())

В этом случае у вас не было отдельного хранилища для кода и даты, но вы все равно не могли конвертировать между двумя указателями (если не считать нестандартных модификаторов __near и __far).

Кроме того, нет гарантии, что даже если указатели имеют одинаковый размер, они указывают на одно и то же — в модели памяти DOS Small, и код, и данные используются рядом с указателями, но они указывают на разные сегменты. Таким образом, преобразование указателя функции в указатель данных не даст вам указатель, который вообще имеет какое-либо отношение к функции, и, следовательно, такое преобразование бесполезно.

30

Предполагается, что указатели на void способны разместить указатель на любой тип данных, но не обязательно указатель на функцию. В некоторых системах требования к указателям на функции отличаются от требований к указателям на данные (например, существуют DSP с разной адресацией данных и кода, средняя модель в MS-DOS использовала 32-разрядные указатели для кода, но только 16-разрядные указатели для данных) ,

23

В дополнение к тому, что уже сказано здесь, интересно посмотреть на POSIX dlsym():

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

 fptr = (int (*)(int))dlsym(handle, "my_function");

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

12

C ++ 11 имеет решение давнего несоответствия между C / C ++ и POSIX в отношении dlsym(), Можно использовать reinterpret_cast преобразовать указатель функции в / из указателя данных, если реализация поддерживает эту функцию.

Из стандарта 5.2.10 абз. 8 «условно поддерживается преобразование указателя функции в тип указателя объекта или наоборот». 1.3.5 определяет «условно поддерживаемую» как «программную конструкцию, которую реализация не обязана поддерживать».

9

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

7

undefined не обязательно означает недопустимый, это может означать, что у разработчика компилятора есть больше свободы делать это так, как они хотят.

Например, это может быть невозможно на некоторых архитектурах — undefined позволяет им по-прежнему иметь соответствующую библиотеку ‘C’, даже если вы не можете этого сделать.

5
По вопросам рекламы [email protected]