Я читал, что преобразование указателя функции в указатель данных и наоборот работает на большинстве платформ, но не гарантированно работает. Почему это так? Не должны ли оба быть просто адресами в основной памяти и, следовательно, быть совместимыми?
Архитектура не должна хранить код и данные в одной и той же памяти. Благодаря гарвардской архитектуре код и данные хранятся в совершенно другой памяти. Большинство архитектур являются архитектурами фон Неймана с кодом и данными в одной и той же памяти, но C не ограничивает себя только определенными типами архитектур, если это вообще возможно.
Некоторые компьютеры имеют (имели) отдельные адресные пространства для кода и данных. На таком оборудовании это просто не работает.
Язык предназначен не только для современных настольных приложений, но и для того, чтобы его можно было реализовать на большом наборе оборудования.
Похоже, комитет по языку C никогда не собирался void*
чтобы быть указателем на функцию, они просто хотели общий указатель на объекты.
Обоснование C99 говорит:
6.3.2.3. Указатели
В настоящее время C реализован на широком спектре архитектур. Хотя некоторые из них
в архитектурах используются одинаковые указатели, максимально соответствующие размеру целочисленного типа
Переносимый код не может предполагать какого-либо необходимого соответствия между различными типами указателей и целочисленными типами. В некоторых реализациях указатели могут быть даже шире, чем любой целочисленный тип.Использование
void*
(Указатель наvoid
») Как типовой указатель на объект является изобретением комитета C89. Принятие этого типа стимулировалось желанием указать аргументы прототипа функции, которые либо тихо преобразуют произвольные указатели (как вfread
) или жаловаться, если тип аргумента не совсем совпадает (как вstrcmp
). Ничего не сказано об указателях на функции, которые могут быть несопоставимы с указателями на объекты и / или целыми числами.
Заметка Ничего не сказано об указателях на функции в последнем абзаце. Они могут отличаться от других указателей, и комитет об этом знает.
Для тех, кто помнит MS-DOS, Windows 3.1 и старше, ответ довольно прост. Все они использовали для поддержки нескольких различных моделей памяти с различными комбинациями характеристик для кода и указателей данных.
Например, для модели Compact (маленький код, большие данные):
sizeof(void *) > sizeof(void(*)())
и наоборот в модели Medium (большой код, маленькие данные):
sizeof(void *) < sizeof(void(*)())
В этом случае у вас не было отдельного хранилища для кода и даты, но вы все равно не могли конвертировать между двумя указателями (если не считать нестандартных модификаторов __near и __far).
Кроме того, нет гарантии, что даже если указатели имеют одинаковый размер, они указывают на одно и то же — в модели памяти DOS Small, и код, и данные используются рядом с указателями, но они указывают на разные сегменты. Таким образом, преобразование указателя функции в указатель данных не даст вам указатель, который вообще имеет какое-либо отношение к функции, и, следовательно, такое преобразование бесполезно.
Предполагается, что указатели на void способны разместить указатель на любой тип данных, но не обязательно указатель на функцию. В некоторых системах требования к указателям на функции отличаются от требований к указателям на данные (например, существуют DSP с разной адресацией данных и кода, средняя модель в MS-DOS использовала 32-разрядные указатели для кода, но только 16-разрядные указатели для данных) ,
В дополнение к тому, что уже сказано здесь, интересно посмотреть на POSIX dlsym()
:
Стандарт ISO C не требует, чтобы указатели на функции могли приводиться назад и вперед к указателям на данные. Действительно, стандарт ISO C не требует, чтобы объект типа void * мог содержать указатель на функцию. Реализации, поддерживающие расширение XSI, однако, требуют, чтобы объект типа void * мог содержать указатель на функцию. Однако результат преобразования указателя на функцию в указатель на другой тип данных (кроме void *) все еще не определен. Обратите внимание, что компиляторы, соответствующие стандарту ISO C, должны генерировать предупреждение, если выполняется попытка преобразования из указателя void * в указатель функции, как в:
fptr = (int (*)(int))dlsym(handle, "my_function");
Из-за проблемы, отмеченной здесь, будущая версия может либо добавить новую функцию для возврата указателей на функции, либо текущий интерфейс может быть устаревшим в пользу двух новых функций: одна возвращает указатели данных, а другая возвращает указатели функций.
C ++ 11 имеет решение давнего несоответствия между C / C ++ и POSIX в отношении dlsym()
, Можно использовать reinterpret_cast
преобразовать указатель функции в / из указателя данных, если реализация поддерживает эту функцию.
Из стандарта 5.2.10 абз. 8 «условно поддерживается преобразование указателя функции в тип указателя объекта или наоборот». 1.3.5 определяет «условно поддерживаемую» как «программную конструкцию, которую реализация не обязана поддерживать».
В зависимости от целевой архитектуры код и данные могут храниться в принципиально несовместимых, физически различных областях памяти.
undefined не обязательно означает недопустимый, это может означать, что у разработчика компилятора есть больше свободы делать это так, как они хотят.
Например, это может быть невозможно на некоторых архитектурах — undefined позволяет им по-прежнему иметь соответствующую библиотеку ‘C’, даже если вы не можете этого сделать.