reinterpret_cast для void * не работает с указателями на функции - PullRequest
6 голосов
/ 20 августа 2009

Я хочу переинтерпретировать приведение указателя функции в переменную void *. Тип указателя функции будет иметь тип Class* (*)(void*).

Ниже приведен пример кода,

class Test
{
    int a;
};

int main()
{
    Test* *p(void **a);
    void *f=reinterpret_cast<void*>(p);     
}

Приведенный выше код хорошо работает с компиляторами Visual Studio / x86. Но с ARM-компилятором это дает ошибку компиляции. Не знаю почему.

Ошибка: # 694: reinterpret_cast не может выбрасывать const или другой тип классификаторы

Я прочитал объяснение в Приведение указателя функции к другому типу

Я был обеспокоен приведенным ниже объяснением.

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

Как сделать такие преобразования из void (*)(void*) -> void* эффективно, чтобы по крайней мере он компилировался почти одинаково в большинстве компиляторов?

Ответы [ 5 ]

7 голосов
/ 20 августа 2009

reinterpret_cast может использоваться только для

  • добавить константу
  • преобразовать указатель в целочисленный тип, достаточно большой, чтобы удерживать его и обратно
  • преобразовать указатель на функцию в указатель на функцию другого типа
  • преобразовать указатель на объект в указатель на объект другого типа
  • преобразовать указатель на функцию-член в указатель на функцию-член другого типа
  • преобразовать указатель на объект-член в указатель на объект-член другого типа
  • и reinterpret_cast<T&>(x) эквивалентны *reinterpret_cast<T*>(&x) (с использованием встроенных & и *) всякий раз, когда возможно второе приведение с использованием вышеуказанных правил.

(см. Раздел 5.2.10 стандарта)

Это означает, в частности, что приведение от указателя к функции на void * невозможно, но вы можете привести его к void(*)().


РЕДАКТИРОВАТЬ (2017): Ответ выше является правильным только для C ++ 03. В C ++ 11 - C ++ 17 это определяется реализацией, если разрешены преобразования между указателями на функции и void *. Это обычно имеет место в POSIX-совместимых системах, поскольку объявлено, что dlsym() возвращает void *, и ожидается, что клиенты reinterpret_cast вернут правильный тип указателя функции.

Полный список разрешенных преобразований см. cppreference.com .

7 голосов
/ 20 августа 2009

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

В C приведение разрешено, но его поведение не определено (т. Е. Даже круговая передача не гарантируется).

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

Я играл с несколькими компиляторами, которые я здесь:

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

В последнем доступном варианте для C ++ 0X условно поддерживается reinterpret_cast между указателями на функции и указателями объектов.

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

Как и другие сделали замечание,

Test* *p(void **a);

определяет функцию, а не указатель на функцию. Но функция для указателя на функцию неявного преобразования сделана для аргумента reinterpret_cast, так что get для reinterpret_cast представляет собой Test** (*p)(void** a).

Благодаря Ричарду, который заставляет меня вернуться к этой проблеме более подробно (для записи, я ошибся, думая, что указатель на функцию, указывающий на объект, был одним из случаев, когда приведение C допускало что-то, не санкционированное C ++, вызывает комбинации) .

4 голосов
/ 20 августа 2009

Если вы просто хотите сохранить указатель на функцию разных типов в списке, вы можете привести к общему типу указателя на функцию:

class Test {
  int a;
};

int main()
{
  Test* *p(void **a);
  void (*f)()=reinterpret_cast<void (*)()>(p);
}

Это действительно для reinterpret_cast (5.2.10 / 6):

Указатель на функцию может быть явно преобразован в указатель на функцию другого типа. Эффект вызова функции через указатель на тип функции (8.3.5), который отличается от типа, используемого в определении функции, не определен. За исключением того, что преобразование значения типа «указатель на T1» в тип «указатель на T2» (где T1 и T2 являются типами функций) и обратно в его исходный тип дает исходное значение указателя, результат такого преобразования указателя не определен .

3 голосов
/ 20 августа 2009

Другие отметили, что вы не можете выполнять это приведение (строго говоря, приведение к void* что-либо, использующее reinterpret_cast, также не разрешено, но компиляторы молча допускают. static_cast предназначено для использования здесь).

Я обычно делаю следующее, что делает каламбур, и это рекомендуемый способ сделать это, в соответствии с man-страницей dlopen (которая предназначена для выполнения обратного чтения из void* до указатель функции). Взяв адрес указателя функции, вы получите указатель на данные: указатель на указатель на функцию. Это позволит вам привести его к void*. Он делает вид, что указывает на void* (вместо указателя функции), а затем читает его.

Test* (*pF)(void **a);
void *p = *(void**)(void*)&pF;

Промежуточное приведение к void* делает его эквивалентным использованию двух static_cast внутри, и заставляет GCC молчать об предупреждении о типе каламбура. При использовании приведения в стиле C ++ это выглядит как комбинация двух static_cast

void *p = *static_cast<void**>(static_cast<void*>(&pF));

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


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

// using the function declaration you provided
Test** pF(void **a); 
void *p = *(void**)(void *) &(Test** (* const&)(void **a))&pF;

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

При использовании явного приведения в стиле C ++ static_cast приведение выглядит намного сложнее, потому что нужно учитывать постоянство. Приведение в стиле C автоматически с этим справляется. Веселись!

int main() {
    Test** pF(void **a);
    void *p = *static_cast<void* const*>(
      static_cast<void const*>(
        &static_cast<Test** (* const&)(void **a)>(&pF)));
}
0 голосов
/ 20 августа 2009

не должно Test* *p(void **a); быть Test* (*p)(void **a)?

Может, в этом твоя проблема?

...