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

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

Ответ 1

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

Ответ 2

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

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


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

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

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

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

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

Ответ 3

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

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

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

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

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

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

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

Ответ 4

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

Ответ 5

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

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

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

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

Ответ 6

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

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

Ответ 7

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

Ответ 8

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

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

Ответ 9

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

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

Ответ 10

Другое решение:

Предполагая, что POSIX гарантирует, что указатели на функцию и данные будут иметь одинаковый размер и представление (я не могу найти текст для этого, но пример, приведенный в OP, предполагает, что они, по крайней мере, предназначены для выполнения этого требования), должно работать следующее:/p >

double (*cosine)(double);
void *tmp;
handle = dlopen("libm.so", RTLD_LAZY);
tmp = dlsym(handle, "cos");
memcpy(&cosine, &tmp, sizeof cosine);

Это позволяет избежать нарушения правил псевдонимов, перейдя через представление char [], которому разрешено псевдоним всех типов.

Еще один подход:

union {
    double (*fptr)(double);
    void *dptr;
} u;
u.dptr = dlsym(handle, "cos");
cosine = u.fptr;

Но я бы рекомендовал подход memcpy, если вы хотите абсолютно 100% правильно C.

Ответ 11

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

struct module foo_module = {
    .create = create_func,
    .destroy = destroy_func,
    .write = write_func,
    /* ... */
};

а затем в вашем приложении:

struct module *foo = dlsym(handle, "foo_module");
foo->create(/*...*/);
/* ... */

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

Ответ 12

В большинстве архитектур указатели на все нормальные типы данных имеют одно и то же представление, поэтому выбор между типами указателей данных является no-op.

Однако, возможно, что указатели на функции могут потребовать другого представления, возможно, они больше других указателей. Если void * может содержать указатели на функции, это означает, что представление void * должно быть большего размера. И все приведения указателей данных в/из void * должны были бы выполнить эту дополнительную копию.

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

Ответ 13

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

Непосредственно цитируется https://blogs.msdn.microsoft.com/oldnewthing/20040209-00/?p=40713/

class Base1 { int b1; void Base1Method(); };
class Base2 { int b2; void Base2Method(); };
class Derived : public Base1, Base2 { int d; void DerivedMethod(); };

В настоящее время существует два возможных указателя this.

Указатель на функцию-член Base1 может использоваться как указатель на член функции Derived, так как они оба используют один и тот же thisуказатель. Но указатель на функцию-член Base2 нельзя использовать as-is как указатель на функцию-член Derived, так как thisнужно отрегулировать указатель.

Существует много способов решения этого. Здесь, как Visual Studio компилятор решает его обработать:

Указатель на функцию-член многократно унаследованного класса действительно структуры.

[Address of function]
[Adjustor]

Размер функции-указателя для класса, использующего множественное наследование, - это размер указателя плюс размер size_t.

tl; dr: При использовании множественного наследования указатель на функцию-член может (в зависимости от компилятора, версии, архитектуры и т.д.) фактически храниться как

struct { 
    void * func;
    size_t offset;
}

который, очевидно, больше, чем a void *.

Ответ 14

Я знаю, что это не было прокомментировано с 2012 года, но я подумал, что было бы полезно добавить, что я знаю архитектуру, которая имеет очень несовместимые указатели на данные и функции, поскольку вызов этой архитектуры проверяет привилегию и переносит Дополнительная информация. Никакое количество кастингов не поможет. Это The Mill.