Подтвердить что ты не робот

Reinterpret_cast против C-стиля

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

4b9b3361

Ответ 1

Листинг C-стиля не лучше.

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

  • он может делать много разных вещей, и не всегда ясно, читает ли код, какой тип приведения будет вызываться (он может вести себя как reinterpret_cast, a 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

Ответ 2

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


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

Однако, как говорится в стандарте,

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

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

Приведение в стиле C несколько похоже на то, что он может выполнять reinterpret_cast, но он также "пытается" static_cast первым, и он может отбрасывать квалификацию cv (в то время как static_cast и reinterpret_cast не могут) и выполнять преобразования без учета контроля доступа (см. 5.4/4 в стандарте С++ 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 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 привело к правильному указателю смещения.

Ответ 3

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

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

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

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

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

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

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

Ответ 4

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

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

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

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

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

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

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

Ответ 5

Оба метода reinterpret_cast и c-style - это реализация, и они делают почти одно и то же. Различия:
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-style cast