Меня немного смущает применимость reinterpret_cast
против static_cast
. Из того, что я прочитал, общие правила заключаются в использовании статического приведения, когда типы могут интерпретироваться во время компиляции, отсюда и слово static
. Это приведение, которое компилятор C ++ использует внутренне для неявных приведений.
reinterpret_cast
Они применимы в двух сценариях:
- конвертировать целочисленные типы в типы указателей и наоборот
- преобразовать один тип указателя в другой. Общая идея, которую я получаю, заключается в том, что это непереносимо, и его следует избегать.
Там, где я немного запутался, это одно использование, которое мне нужно, я вызываю C ++ из C, а код C должен удерживать объект C ++, поэтому в основном он содержит a void*
. Какой void *
тип приведения следует использовать для преобразования между типом и классом?
Я видел использование обоих static_cast
и reinterpret_cast
? Хотя из того, что я читал, кажется, static
что лучше, поскольку приведение может произойти во время компиляции? Хотя он говорит использовать reinterpret_cast
для преобразования из одного типа указателя в другой?
reinterpret_cast
не происходит во время выполнения. Они оба являются инструкциями времени компиляции. От en.cppreference.com/w/cpp/language/reinterpret_cast : «В отличие от static_cast, но, как и const_cast, выражение reinterpret_cast не компилируется ни в какие инструкции процессора. Это чисто директива компилятора, которая инструктирует компилятор обрабатывать последовательность битов (представление объекта) выражения, как если бы оно имело тип new_type. "Ответы:
Стандарт C ++ гарантирует следующее:
static_cast
указатель на и отvoid*
сохраняет адрес. То есть, в дальнейшемa
,b
иc
все они указывают на тот же адрес:reinterpret_cast
только гарантирует, что если вы приведете указатель к другому типу, а затемreinterpret_cast
вернетесь к исходному типу , вы получите исходное значение. Итак, в следующем:a
иc
содержат то же значение, но значениеb
не указано. (на практике он обычно содержит тот же адрес, чтоa
иc
, но это не указано в стандарте, и это может быть неверно на машинах с более сложными системами памяти.)Для приведения к и от
void*
,static_cast
должно быть предпочтительным.источник
b
больше не является неопределенным в C ++ 11 при использованииreinterpret_cast
. И в C ++ 03 слепокint*
кvoid*
было запрещено делать сreinterpret_cast
(хотя составители не выполнили это , и это было непрактично, поэтому было изменено на C ++ 11).Один случай, когда
reinterpret_cast
это необходимо, - это взаимодействие с непрозрачными типами данных. Это часто происходит в API поставщиков, над которыми программист не имеет никакого контроля. Вот надуманный пример, в котором поставщик предоставляет API для хранения и извлечения произвольных глобальных данных:Чтобы использовать этот API, программист должен привести свои данные
VendorGlobalUserData
обратно и обратно.static_cast
не будет работать, нужно использоватьreinterpret_cast
:Ниже приведена надуманная реализация примера API:
источник
void*
для этого?USpoofChecker*
, гдеUSpoofChecker
пустая структура. Однако под капотом, когда вы передаете aUSpoofChecker*
, он подвергаетсяreinterpret_cast
внутреннему типу C ++.Краткий ответ: если вы не знаете, что
reinterpret_cast
означает, не используйте его. Если вам это понадобится в будущем, вы будете знать.Полный ответ:
Давайте рассмотрим основные типы чисел.
Например, когда вы конвертируете
int(12)
вunsigned float (12.0f)
свой процессор, необходимо выполнить некоторые вычисления, поскольку оба числа имеют разное представление битов. Это то, чтоstatic_cast
означает.С другой стороны, при вызове
reinterpret_cast
ЦП не вызывает никаких вычислений. Он просто обрабатывает набор битов в памяти, как если бы он имел другой тип. Поэтому при преобразованииint*
вfloat*
это ключевое слово новое значение (после разыменования указателя) не имеет ничего общего со старым значением в математическом смысле.Пример: это правда, что
reinterpret_cast
не является переносимым по одной причине - порядок байтов (порядковый номер). Но это часто удивительно лучшая причина для его использования. Давайте представим пример: вы должны прочитать двоичное 32-битное число из файла, и вы знаете, что это порядковый номер. Ваш код должен быть универсальным и работать должным образом в системах с прямым порядком байтов (например, в некоторых ARM) и байтовых порядках (например, в x86). Таким образом, вы должны проверить порядок байтов.Это хорошо известно во время компиляции, поэтому вы можете написатьвы можете написать функцию для достижения этой цели:constexpr
функцию:Объяснение: двоичное представление
x
в памяти может быть0000'0000'0000'0001
(большим) или0000'0001'0000'0000
(прямым порядком байтов). После переинтерпретации приведение байта подp
указатель может быть соответственно0000'0000
или0000'0001
. Если вы используете статическое приведение, оно всегда будет0000'0001
, независимо от того, какая последовательность используется.РЕДАКТИРОВАТЬ:
В первой версии я сделал пример функции
is_little_endian
бытьconstexpr
. Он прекрасно компилируется на новейшем gcc (8.3.0), но стандарт говорит, что это незаконно. Компилятор clang отказывается компилировать его (что правильно).источник
short
занимает 16 бит в памяти. Исправлено.Значение
reinterpret_cast
не определяется стандартом C ++. Следовательно, теоретически areinterpret_cast
может привести к сбою вашей программы. На практике компиляторы пытаются делать то, что вы ожидаете, то есть интерпретировать биты того, что вы передаете, как если бы они были типом, к которому вы приводите. Если вы знаете, что делать с компиляторами, которые вы собираетесь использовать,reinterpret_cast
вы можете использовать его, но сказать, что он переносим, было бы ложью.Для случая, который вы описываете, и в значительной степени для любого случая, когда вы могли бы рассмотреть
reinterpret_cast
, вы можете использоватьstatic_cast
или другую альтернативу. Среди прочего, в стандарте есть, что сказать о том, чего вы можете ожидатьstatic_cast
(§5.2.9):Таким образом, для вашего случая использования достаточно ясно, что комитет по стандартизации предназначен для вас
static_cast
.источник
reinterpret_crash
. Ни в коем случае ошибка компилятора не помешает мне разбить мою программу интерпретации. Яtemplate<class T, U> T reinterpret_crash(U a) { return *(T*)nullptr; }
Одно из применений reinterpret_cast - это если вы хотите применять побитовые операции к (IEEE 754) числам с плавающей точкой. Одним из примеров этого был трюк Fast Inverse Square-Root:
https://en.wikipedia.org/wiki/Fast_inverse_square_root#Overview_of_the_code
Он рассматривает двоичное представление числа с плавающей точкой как целое число, сдвигает его вправо и вычитает его из константы, тем самым вдвое уменьшая и отрицая показатель степени. После преобразования обратно в число с плавающей точкой он подвергается итерации Ньютона-Рафсона, чтобы сделать это приближение более точным:
Первоначально он был написан на C, поэтому использует приведение C, но аналогичным приведением C ++ является reinterpret_cast.
источник
error: invalid cast of an rvalue expression of type 'int64_t {aka long long int}' to type 'double&' reinterpret_cast<double&>((reinterpret_cast<int64_t&>(d) >> 1) + (1L << 61))
- ideone.com/6S4ijcreinterpret_cast
наmemcpy
, это все еще UB?memcpy
определенно сделает это законным.Вы можете использовать reinterprete_cast для проверки наследования во время компиляции.
Посмотрите здесь: Использование reinterpret_cast для проверки наследования во время компиляции
источник
Я попытался заключить и написал простое безопасное приведение, используя шаблоны. Обратите внимание, что это решение не гарантирует наведение указателей на функции.
источник
reinterpret_cast
уже происходит в этой ситуации: «Указатель объекта может быть явно преобразован в указатель объекта другого типа. [72] Когда значениеv
типа указателя объекта преобразуется в тип указателя объекта« указатель на cvT
», результатstatic_cast<cv T*>(static_cast<cv void*>(v))
". - N3797.c++2003
стандарта я НЕ считаю , чтоreinterpret_cast
делаетstatic_cast<cv T*>(static_cast<cv void*>(v))
C++03
этого былоC++98
. Тонны проектов использовали старый C ++ вместо переносимого C. Иногда вам нужно заботиться о переносимости. Например, вы должны поддерживать один и тот же код в Solaris, AIX, HPUX, Windows. Что касается зависимости и переносимости компилятора, то это сложно. Так что хороший пример введения ада переносимости - использованиеreinterpret_cast
в вашем кодеСначала у вас есть данные определенного типа, например, int:
Затем вы хотите получить доступ к той же переменной, что и другой тип, такой как float: вы можете выбрать между
или
КРАТКОЕ ОПИСАНИЕ: это означает, что одна и та же память используется как другой тип. Таким образом, вы можете преобразовать двоичные представления типов с плавающей точкой, как указано выше, в типы с плавающей точкой. Например, 0x80000000 равно -0 (мантисса и показатель степени равны нулю, но знак msb равен единице. Это также работает для двойных и длинных двойных.
ОПТИМИЗАЦИЯ: Я думаю, что reinterpret_cast будет оптимизирован во многих компиляторах, в то время как c-приведение выполняется с помощью pointerarithmetic (значение должно быть скопировано в память, потому что указатели не могут указывать на cpu-регистры).
ПРИМЕЧАНИЕ: в обоих случаях вы должны сохранить приведенное значение в переменной перед приведением! Этот макрос может помочь:
источник
reinterpret_cast
формаint
tofloat&
является неопределенным поведением.Одна из причин использования
reinterpret_cast
- это когда базовый класс не имеет vtable, но у производного класса есть. В этом случаеstatic_cast
иreinterpret_cast
приведет к различным значениям указателя (это будет нетипичный случай, упомянутый выше jalf ). Как отказ от ответственности, я не утверждаю, что это является частью стандарта, а является реализацией нескольких распространенных компиляторов.В качестве примера возьмем код ниже:
Который выводит что-то вроде:
Во всех компиляторах, которые я пробовал (MSVC 2015 и 2017, clang 8.0.0, gcc 9.2, icc 19.0.1 - см. Godbolt для последних 3 ), результат
static_cast
отличается отreinterpret_cast
результата 2 (4 для MSVC). Единственный компилятор, который предупреждал о разнице, был clang, с:Последнее предостережение заключается в том, что если базовый класс не имеет членов данных (например,
int i;
), то clang, gcc и icc возвращают тот же адрес,reinterpret_cast
что и дляstatic_cast
, тогда как MSVC все еще не возвращает .источник
Вот вариант программы Ави Гинзбурга, который ясно иллюстрирует свойство,
reinterpret_cast
упомянутое Крисом Луенго, flodin и cmdLP: компилятор обрабатывает указанную ячейку памяти, как если бы это был объект нового типа:Что приводит к выводу, как это:
Можно видеть, что объект B строится в памяти сначала как данные, специфичные для B, а затем внедряется объект A.
static_cast
Правильно возвращает адрес объекта , привязанного A, и указатель , созданныйstatic_cast
правильно дает значение поля данных. Указатель, сгенерированный объектом,reinterpret_cast
обрабатывает областьb
памяти, как если бы это был простой объект A, и поэтому, когда указатель пытается получить поле данных, он возвращает некоторые специфичные для B данные, как если бы это было содержимое этого поля.Одним из применений
reinterpret_cast
является преобразование указателя в целое число без знака (когда указатели и целые числа без знака имеют одинаковый размер):int i;
unsigned int u = reinterpret_cast<unsigned int>(&i);
источник
Быстрый ответ: используйте,
static_cast
если он компилируется, в противном случае прибегайте кreinterpret_cast
.источник
Прочитайте FAQ ! Хранение данных C ++ в C может быть рискованным.
В C ++ указатель на объект может быть преобразован
void *
без приведения. Но это не так, наоборот. Вам нужноstatic_cast
вернуть исходный указатель.источник