Является ли reinterpret_cast в основном бесполезным?

Я читал различные предыдущие вопросы об использовании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 ответов

Решение

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

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

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

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

По сути, это сводится к тому, что результат указатель на указатель 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) и требования к выравниванию T2 не являются более строгими, чем требования для T1.

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

Значение типа "указатель на cv1 void" может быть преобразовано в значение типа "указатель на cv2 T", где T - это тип объекта, а cv2 - та же квалификация cv, что и cv1 или более высокая квалификация, чем 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 минуты я написал лучшую спецификацию, чем комитет за десять лет.

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

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

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

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

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

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

Другие вопросы по тегам