Я читал, что преобразование указателя функции в указатель данных и наоборот работает на большинстве платформ, но не гарантируется. Почему это так? Разве оба не должны быть просто адресами в основной памяти и, следовательно, быть совместимыми?
c++
c
pointers
function-pointers
gexicide
источник
источник
void
. Преобразование указателя функции вvoid *
не должно изменять представление.void *
Значение в результате такого преобразования может быть преобразована обратно в исходный тип указателя функции, используя явное приведение, без потери информации. Примечание . Стандарт ISO C этого не требует, но он необходим для соответствия POSIX.dlsym()
- обратите внимание на конец раздела «Использование приложения», где говорится: Обратите внимание, что преобразование изvoid *
указателя в указатель функции, как в:,fptr = (int (*)(int))dlsym(handle, "my_function");
не определено стандартом ISO C. Этот стандарт требует, чтобы это преобразование работало правильно в соответствующих реализациях.Ответы:
Архитектура не обязана хранить код и данные в одной и той же памяти. В гарвардской архитектуре код и данные хранятся в совершенно другой памяти. Большинство архитектур - это архитектуры фон Неймана с кодом и данными в одной и той же памяти, но C не ограничивается только определенными типами архитектур, если это вообще возможно.
источник
CS != DS
).VirtualProtect
, что позволяет вам отмечать области данных как исполняемые.Некоторые компьютеры имеют (имели) отдельные адресные пространства для кода и данных. На таком оборудовании просто не работает.
Этот язык разработан не только для текущих настольных приложений, но и позволяет реализовать его на большом количестве оборудования.
Похоже, что комитет по языку C никогда не намеревался
void*
быть указателем на функцию, им просто нужен общий указатель на объекты.Обоснование C99 гласит:
Примечание. В последнем абзаце ничего не говорится об указателях на функции . Они могут отличаться от других указателей, и комитет это знает.
источник
void *
.sizeof(void*) == sizeof( void(*)() )
приведет к потере места в случае, когда указатели функций и указатели данных имеют разные размеры. Это было обычным делом в 80-х, когда был написан первый стандарт C.Для тех, кто помнит MS-DOS, Windows 3.1 и старше, ответ довольно прост. Все они используются для поддержки нескольких различных моделей памяти с различными комбинациями характеристик для кода и указателей данных.
Так, например, для модели Compact (небольшой код, большие данные):
и наоборот, в модели Medium (большой код, небольшие данные):
В этом случае у вас не было отдельного хранилища для кода и даты, но вы все равно не могли конвертировать между двумя указателями (за исключением использования нестандартных модификаторов __near и __far).
Кроме того, нет гарантии, что даже если указатели имеют одинаковый размер, они указывают на одно и то же - в модели памяти DOS Small и код, и данные используются рядом с указателями, но они указывают на разные сегменты. Таким образом, преобразование указателя функции в указатель данных не даст вам указатель, который вообще имеет какое-либо отношение к функции, и, следовательно, такое преобразование было бесполезно.
источник
int*
в avoid*
дает вам указатель, с которым вы действительно ничего не можете сделать, но все же полезно иметь возможность выполнить преобразование. (Это потому, чтоvoid*
может хранить любой указатель на объект, поэтому его можно использовать для общих алгоритмов, которым не нужно знать, какой тип они хранят. То же самое могло бы быть полезно и для указателей на функции, если бы это было разрешено.)int *
кvoid *
, тоvoid *
гарантируется , по крайней мере , указывают на тот же объект , как оригиналint *
сделал - так что это полезно для общих алгоритмов, доступ заостренный к объекту, напримерint n; memcpy(&n, src, sizeof n);
. В случае, когда преобразование указателя функции в avoid *
не дает указателя, указывающего на функцию, это бесполезно для таких алгоритмов - единственное, что вы могли бы сделать, этоvoid *
снова преобразовать обратно в указатель функции, поэтому вы можете как ну просто используйте указатель,union
содержащийvoid *
и функцию.void*
действительно указывал на функцию, я полагаю, людям было бы плохой идеей передавать ееmemcpy
. :-Pvoid
. Преобразование указателя функции вvoid *
не должно изменять представление.void *
Значение в результате такого преобразования может быть преобразована обратно в исходный тип указателя функции, используя явное приведение, без потери информации. Примечание . Стандарт ISO C этого не требует, но он необходим для соответствия POSIX.Предполагается, что указатели на void могут содержать указатель на любые данные, но не обязательно указатель на функцию. В некоторых системах требования к указателям на функции отличаются от требований к указателям на данные (например, существуют DSP с разной адресацией для данных и кода, в средней модели в MS-DOS используются 32-разрядные указатели для кода, но только 16-разрядные указатели для данных) ,
источник
Помимо того, что здесь уже сказано, интересно посмотреть на POSIX
dlsym()
:источник
void*
и обратно.void*
совместимости с указателем на функцию, тогда как POSIX требует.В C ++ 11 есть решение давнего несоответствия между C / C ++ и POSIX в отношении
dlsym()
. Можно использоватьreinterpret_cast
для преобразования указателя функции в / из указателя данных, если реализация поддерживает эту функцию.Из стандарта, п. 5.2.10 п. 8, «условно поддерживается преобразование указателя функции в тип указателя объекта или наоборот». 1.3.5 определяет «условно поддерживаемую» как «конструкцию программы, которую реализация не обязана поддерживать».
источник
-Werror
). Лучшее (и не относящееся к UB) решение - получить указатель на объект, возвращаемыйdlsym
(т.е.void**
), и преобразовать его в указатель на указатель функции . По-прежнему определяется реализацией, но больше не вызывает предупреждения / ошибки .dlsym
иGetProcAddress
компилировать без предупреждения.-pedantic
) делает предупредить. Опять же, никаких предположений невозможно.В зависимости от целевой архитектуры код и данные могут храниться в принципиально несовместимых, физически различных областях памяти.
источник
void *
достаточно большой, чтобы содержать любой указатель данных, но не обязательно указатель на функцию.undefined не обязательно означает «не разрешено», это может означать, что разработчик компилятора имеет больше свободы делать это так, как они хотят.
Например, это может быть невозможно на некоторых архитектурах - undefined позволяет им по-прежнему иметь соответствующую библиотеку C, даже если вы не можете этого сделать.
источник
Другое решение:
Предполагая, что POSIX гарантирует, что указатели функций и данных имеют одинаковый размер и представление (я не могу найти текст для этого, но приведенный пример OP предполагает, что они, по крайней мере, намеревались выполнить это требование), следующее должно работать:
Это позволяет избежать нарушения правил создания псевдонимов путем прохождения
char []
представления, которому разрешено использовать псевдонимы для всех типов.Еще один подход:
Но я бы порекомендовал этот
memcpy
подход, если вы хотите абсолютно 100% правильного C.источник
Они могут быть разных типов с разной занимаемой площадью. Присвоение одному может необратимо сократить значение указателя, так что возвращение приведет к чему-то другому.
Я считаю, что они могут быть разных типов, потому что стандарт не хочет ограничивать возможные реализации, которые экономят место, когда оно не нужно или когда размер может привести к тому, что ЦП будет вынужден делать лишнюю ерунду для его использования и т. Д.
источник
Единственное действительно переносимое решение - не использовать
dlsym
для функций, а вместо этого использоватьdlsym
для получения указателя на данные, содержащие указатели на функции. Например, в вашей библиотеке:а затем в вашем приложении:
Между прочим, это в любом случае хорошая практика проектирования, которая позволяет легко поддерживать как динамическую загрузку, так
dlopen
и статическое связывание всех модулей в системах, которые не поддерживают динамическое связывание или где пользователь / системный интегратор не хочет использовать динамическое связывание.источник
foo_module
структуру (с уникальными именами), вы можете просто создать дополнительный файл с массивомstruct { const char *module_name; const struct module *module_funcs; }
и простой функцией для поиска в этой таблице модуля, который вы хотите «загрузить», и возврата правильного указателя, а затем использовать это вместоdlopen
иdlsym
.Современный пример того, где указатели функций могут отличаться по размеру от указателей данных: указатели функций-членов класса C ++
Прямая цитата из https://blogs.msdn.microsoft.com/oldnewthing/20040209-00/?p=40713/
tl; dr: при использовании множественного наследования указатель на функцию-член может (в зависимости от компилятора, версии, архитектуры и т. д.) фактически храниться как
что, очевидно, больше a
void *
.источник
На большинстве архитектур указатели на все обычные типы данных имеют одинаковое представление, поэтому преобразование типов указателей данных не выполняется.
Однако вполне возможно, что указатели функций могут потребовать другого представления, возможно, они больше, чем другие указатели. Если void * может содержать указатели на функции, это будет означать, что представление void * должно быть большего размера. И все приведения указателей данных в / из void * должны будут выполнять эту дополнительную копию.
Как кто-то сказал, если вам это нужно, вы можете добиться этого с помощью союза. Но в большинстве случаев void * используется только для данных, поэтому было бы обременительно увеличивать объем используемой ими памяти на тот случай, если необходимо сохранить указатель на функцию.
источник
Я знаю , что это не было прокомментировано с 2012 года, но я подумал , что было бы полезно добавить , что я сделать знаю архитектуру , которая имеет очень несовместимые указатели для данных и функций , так как вызов на этой архитектуру проверяет привилегию и несет дополнительную информацию. Никакой кастинг не поможет. Это мельница .
источник