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

Действительно ли reinterpret_cast бесполезен?

Я прочитал различные предыдущие вопросы о использовании reinterpret_cast, и я также прочитал соответствующую формулировку в стандарте С++. По существу, все, что происходит, состоит в том, что результат операции указателя на указатель reinterpret_cast нельзя безопасно использовать для чего-либо иного, кроме того, что он возвращается к исходному типу указателя.

На практике, однако, большинство видов использования reinterpret_cast в реальном мире, по-видимому, основаны на (ошибочном) предположении, что a reinterpret_cast является таким же, как приведение в стиле C. Например, я видел много кода, который использует reinterpret_cast для перевода из char* в unsigned char* для целей подпрограмм преобразования набора символов. Это абсолютно безопасно, но, строго говоря, оно не переносимо - нет гарантии, что reinterpret_cast от char* до unsigned char* не будет разбивать вашу программу при попытке разыменовать указатель unsigned char*.

Кажется, единственное реальное использование reinterpret_cast, которое имеет какие-либо реальные гарантии, согласно стандарту, преобразуется из указателя в целое и наоборот.

И все же есть много случаев, когда мы хотим (и должны быть в состоянии) безопасно конвертировать между разными типами указателей. Например: uint16_t* в новый С++ 0x char16_t* или действительно любой указатель на базовый тип данных, который имеет тот же размер/выравнивание, что и исходный тип. Тем не менее reinterpret_cast не дает никаких гарантий, которые должны работать.

Вопрос:. Как мы можем безопасно конвертировать между указателями в базовые типы данных одинакового размера/выравнивания, например char*unsigned char*? Поскольку reinterpret_cast, похоже, не гарантирует, что это действительно работает, являются ли C-style единственным безопасным вариантом?

4b9b3361

Ответ 1

нет гарантии, что reinterpret_cast от char * до unsigned char * не приведет к сбою вашей программы при попытке разыменовать беззнаковый указатель char *.

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

Так как reinterpret_cast, похоже, не гарантирует, что это действительно работает, C-style использует единственный безопасный вариант здесь?

Листинг C-стиля будет только отображаться на reinterpret_cast, поэтому он будет точно таким же. В какой-то момент вы должны доверять своему компилятору. Стандарт имеет ограничение, на которое он просто говорит: "Нет, прочитайте руководство по компиляции". Когда дело доходит до перекрестных указателей, это такая точка. Он позволяет читать char, используя unsigned char lvalue. Компилятор, который не может использовать char* для использования unsigned char*, чтобы сделать это, просто неприменим и не существует по этой причине.

Ответ 2

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

Вы правы, стандарт был сломан, но N3242 пытается исправить это:

Определение значения reinterpret_cast<T*>

N3242, [expr.reinterpret.cast]:

Когда prvalue v типа "указатель на T1" преобразуется в тип "указатель на cv T2", результатом является static_cast<cv T2*>(static_cast<cv void*>(v)), если оба T1 и T2 являются стандартными типами макета (3.9) и требованиями к выравниванию T2 не являются более строгими, чем T1.

Это ничего не определяет; для удовольствия, менее нерелевантный текст о static_cast:

Значение типа "указатель на cv1 void" может быть преобразовано в prvalue типа "указатель на cv2 T", где T - тип объекта, а cv2 - это та же самая cv-квалификация, что и более высокая cv-квалификация, чем, cv1. Значение нулевого указателя преобразуется в значение нулевого указателя для типа назначения. Значение указателя типа для объекта, преобразованного в "указатель на cv void" и обратно, возможно с другой cv-квалификацией, должно иметь свое первоначальное значение.

"Подходящее преобразование"

N3242, [класс .mem]:

Указатель на объект структуры стандартного макета, подходящий, преобразованный с помощью reinterpret_cast, указывает на его начальный член (или если этот член является битовым полем), то к блоку, в котором он находится) и наоборот.

Что это за стиль? " соответственно"? лол

Ясно, что эти ребята не знают, как написать спецификацию.

C-style использует единственный безопасный вариант здесь?

Это не помогает.

Стандарт сломан, сломан, сломан.

Но все понимают, что это на самом деле означает. Подтекст стандарта гласит:

Когда prvalue v типа "указатель на T1" преобразуется в тип "указатель на cv T2", результат равен static_cast<cv T2*>(static_cast<cv void*>(v)) указывает на адрес памяти как v, если оба T1 и T2 являются стандартными типами макета (3.9), а требования к выравниванию T2 не более строгие, чем требования T1.

Это, конечно, не идеально (но не хуже, чем многие другие части стандарта), но в 2 млн. я написал лучшую спецификацию, чем комитет за десятилетие.

Ответ 3

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

Ответ 4

В стандарте указано, что должно произойти на платформах all, вам этого не нужно. Если вы ограничиваете свои требования к переносимости платформам, на которых работает ваш reinterpret_cast, это прекрасно.

На платформах, которые фактически поддерживают uint16_t, действие, скорее всего, будет работать. На платформах, где char16_t имеет ширину 18, 24, 32 или 36 бит, он может не поступить правильно. Вопрос в том, нужно ли поддерживать такие платформы? Языковой стандарт хочет.

Ответ 5

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

Это звучит не так. Предполагая, что sizeof(void *) > sizeof(int *), void *foo; reinterpret_cast<void *>(reinterpret_cast<int *>(foo)) может оставить вас с укороченным значением указателя.

Разве это не то, что reinterpret_cast - на практике - просто эквивалент C по умолчанию?