Является ли reinterpret_cast в основном бесполезным? - PullRequest
16 голосов
/ 20 февраля 2011

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

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

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

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

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

Ответы [ 5 ]

11 голосов
/ 21 февраля 2011

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

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

Поскольку reinterpret_cast, кажется, не гарантирует, что это действительно работает, они в стиле Cздесь используется единственная безопасная опция?

Приведение в стиле C будет просто отображено на reinterpret_cast, поэтому оно будет точно таким же.В какой-то момент вы должны доверять вашему компилятору.Стандарт имеет ограничение, в котором он просто говорит «нет. Прочитайте руководство вашего компилятора».Когда дело доходит до указателей перекрестного литья, это такой момент.Это позволяет вам читать char, используя unsigned char lvalue.Компилятор, который не может привести char* к пригодному для использования unsigned char*, чтобы сделать это, почти бесполезен и по этой причине не существует.

4 голосов
/ 11 декабря 2011

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

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

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

N3242, [expr.reinterpret.cast]:

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

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

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

"Соответствующим образом преобразованный"

N3242, [class.mem]:

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

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

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

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

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

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

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

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

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

3 голосов
/ 20 февраля 2011

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

2 голосов
/ 20 февраля 2011

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

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

0 голосов
/ 20 февраля 2011

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

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

Не правда ли, reinterpret_cast - на практике - просто эквивалент броска Си по умолчанию?

...