Reinterpret_cast против броска в стиле C - PullRequest
32 голосов
/ 20 октября 2011

Я слышал, что reinterpret_cast определяется реализацией, но я не знаю, что это на самом деле означает. Можете ли вы привести пример того, как это может пойти не так, и не так, лучше ли использовать приведение в стиле C?

Ответы [ 6 ]

31 голосов
/ 20 октября 2011

Приведение в стиле C не лучше.

Он просто пробует различные приведения в стиле C ++ по порядку, пока не найдет тот, который работает.Это означает, что когда он действует как reinterpret_cast, у него возникают те же проблемы, что и у reinterpret_cast.Но кроме того, у него есть следующие проблемы:

  • Он может делать много разных вещей, и из чтения кода не всегда ясно, какой тип приведения будет вызываться (он может вести себя как reinterpret_cast, const_cast или static_cast, и они делают совершенно разные вещи)
  • Следовательно, изменение окружающего кода может изменить поведение приведения
  • Трудно найти при чтении илипоиск по коду - reinterpret_cast легко найти, и это хорошо, потому что приведения являются некрасивыми и на них следует обращать внимание при использовании.И наоборот, приведение в стиле C (как в (int)42.0) гораздо сложнее найти надежным путем поиска

Чтобы ответить на другую часть вашего вопроса, да, reinterpret_cast определяется реализацией.Это означает, что когда вы используете его для преобразования, скажем, int* в float*, у вас нет гарантии, что результирующий указатель будет указывать на тот же адрес.Эта часть определяется реализацией.Но если вы возьмете полученные float* и reinterpret_cast обратно в int*, вы получите исходный указатель.Эта часть гарантирована.

Но, опять же, помните, что это верно, если вы используете reinterpret_cast или приведение в стиле C:

int i;
int* p0 = &i;

float* p1 = (float*)p0; // implementation-defined result
float* p2 = reinterpret_cast<float*>(p0); // implementation-defined result

int* p3 = (int*)p1; // guaranteed that p3 == p0
int* p4 = (int*)p2; // guaranteed that p4 == p0
int* p5 = reinterpret_cast<int*>(p1); // guaranteed that p5 == p0
int* p6 = reinterpret_cast<int*>(p2); // guaranteed that p6 == p0
15 голосов
/ 20 октября 2011

Это реализация, определенная в том смысле, что стандарт не (почти) не предписывает, как значения разных типов должны выглядеть на битовом уровне, как должно быть структурировано адресное пространство и так далее.Так что это действительно очень специфичная платформа для таких преобразований, как:

double d;
int &i = reinterpret_cast<int&>(d);

Однако, как гласит стандарт,

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

Так что, если вы знаете, что делаете и как все это выглядит на низком уровне, ничто не может пойти не так.

Состав в стиле C в некотором смысле похожчто он может выполнять reinterpret_cast, но он также сначала «пытается» static_cast и может отбросить квалификацию cv (в то время как static_cast и reinterpret_cast не могут) и выполнить преобразования без учета контроля доступа (см. 5.4 / 4 в стандарте C ++ 11).Например:

#include <iostream>

using namespace std;

class A { int x; };
class B { int y; };

class C : A, B { int z; };

int main()
{
  C c;

  // just type pun the pointer to c, pointer value will remain the same
  // only it's type is different.
  B *b1 = reinterpret_cast<B *>(&c);

  // perform the conversion with a semantic of static_cast<B*>(&c), disregarding
  // that B is an unaccessible base of C, resulting pointer will point
  // to the B sub-object in c.
  B *b2 = (B*)(&c);

  cout << "reinterpret_cast:\t" << b1 << "\n";
  cout << "C-style cast:\t\t" << b2 << "\n";
  cout << "no cast:\t\t" << &c << "\n";
}

и вот результат от ideone:

reinterpret_cast:  0xbfd84e78
C-style cast:      0xbfd84e7c
no cast:           0xbfd84e78

обратите внимание, что значение, полученное с помощью reinterpret_cast, точно такое же, как и адрес 'c', тогда как стиль Cприведение привело к правильно смещенному указателю.

5 голосов
/ 20 октября 2011

Существуют веские причины для использования reinterpret_cast, и по этим причинам стандарт фактически определяет, что происходит.

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

T b;
intptr_t a = reinterpret_cast<intptr_t>( &b );
T * c = reinterpret_cast<T*>(a);

В этом коде c гарантированно указывает на объект b, как вы и ожидали.Преобразование обратно в другой тип указателя, конечно, не определено (вроде).

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

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

В обоих случаях вам следуетиспользуйте явный оператор reinterpret_cast вместо C-Style.Хотя стиль C обычно делает то же самое, он может быть перегружен операторами преобразования.

3 голосов
/ 20 октября 2011

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

Иногда, однако, вы хотите переосмыслить биты, которые представляют тип, во что-то другое. Это обычно используется для операций очень низкого уровня и не является чем-то, что вы должны обычно использовать. Для этих случаев вы можете использовать reinterpret_cast.

Это определенная реализация, потому что стандарт C ++ на самом деле ничего не говорит о том, как вещи должны быть на самом деле размещены в памяти. Это контролируется вашей конкретной реализацией C ++. Из-за этого поведение reinterpret_cast зависит от того, как ваш компилятор размещает структуры в памяти и как он реализует reinterpret_cast.

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

Простой пример того, как это может пойти не так:

std::string a;
double* b;
b = reinterpret_cast<double*>(&a);
*b = 3.4;

Поведение этой программы не определено - компилятор может делать с ним что угодно. Скорее всего, вы получите сбой, когда вызывается деструктор string, но кто знает! Это может просто повредить ваш стек и вызвать сбой в несвязанной функции.

1 голос
/ 20 октября 2011

Как reinterpret_cast, так и приведение в стиле c определяются реализацией, и они выполняют почти одно и то же Различия:
1. reinterpret_cast не может удалить константность. Например:

const unsigned int d = 5;
int *g=reinterpret_cast< int* >( &d );

выдаст ошибку:

error: reinterpret_cast from type 'const unsigned int*' to type 'int*' casts away qualifiers  

2. Если вы используете reinterpret_cast, легко найти места, где вы это сделали. Невозможно делать приведения в стиле c

0 голосов
/ 09 июля 2018

С-стиль приводит к тому, что объект вводится без указания типа, например, (unsigned int)-1, иногда преобразует одно и то же значение в другой формат, например, (double)42, иногда может выполнять и то и другое, например, как (void*)0xDEADBEEFинтерпретирует биты, но (void*)0 гарантированно является константой нулевого указателя, которая не обязательно имеет то же представление объекта, что и (intptr_t)0, и очень редко говорит компилятору делать что-то вроде shoot_self_in_foot_with((char*)&const_object);.

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

Бьярн Страуструп в своих рекомендациях рекомендовал reinterpret_cast в другом контексте:если вы хотите печатать каламбур так, чтобы язык не определялся с помощью static_cast, он предложил вам сделать это с помощью чего-то вроде reinterpret_cast<double&>(uint64), а не другими методами.Все они неопределенного поведения, но это делает очень четко, что вы делаете, и что вы делаете это нарочно.Чтение другого члена профсоюза, к которому вы в последний раз писали, не дает.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...