Насколько я знаю, reinterpret_cast не должен приводить к потере данных.
Таким образом, такой код невозможно скомпилировать в X86_64, поскольку целое число меньше указателя.
#include <cstdio>
int main() {
int a = 123;
int res = reinterpret_cast<int>(reinterpret_cast<void*>(a));
printf("%d", a == res);
}
Вопрос: почему я могу компилировать такой код в GCC и Clang?
#include <cstdio>
int main() {
__uint128_t a = 4000000000000000000;
a *= 100;
__uint128_t res = reinterpret_cast<__uint128_t>(reinterpret_cast<void*>(a));
printf("%d", a == res);
}
И результат, который я получаю, "0", означает, что есть потеря данных.
Редактировать
Я думаю, что есть 3 возможных варианта, что это может быть. Ошибка компилятора, злоупотребление спецификацией или следствие спецификации. Какой это?
@underscore_d Я думаю, что reinterpret_cast
определенно защищает вас в некоторых случаях. Мне интересно, почему это не делается сейчас.
у вас просто есть 8 старших байтов или около того. это как mov ax , WORD PTR some_dword
в масме
@CPPCPPCPPCPPCPPCPPCPPCPPCPPCPP reinterpret_cast must not lead to data loss
Думаю, вы неправильно это прочитали, и это не единственный случай, когда это может произойти.
@dxiv Можете ли вы показать другие случаи с reinterpret_cast? Я не нашел много.
@CPPCPPCPPCPPCPPCPPCPPCPPCPPCPP Может быть, не «потеря данных» как таковая, но стандарт отмечает , что «отображение, выполненное reinterpret_cast
, может или не может давать представление, отличное от исходного значения». Один пример из cppreference.com заключается в том, что «один и тот же указатель может иметь несколько целочисленных представлений», что на самом деле довольно часто встречается в архитектурах со смещением сегментов.
Это объясняется здесь https://en.cppreference.com/w/cpp/language/reinterpret_cast
- Указатель можно преобразовать в любой целочисленный тип, достаточно большой, чтобы вместить все значения этого типа (например, в std::uintptr_t).
Вот почему у вас есть ошибка для первого случая
- Значение любого интегрального или перечисляемого типа может быть преобразовано в тип указателя...
вот почему у вас нет ошибки, но во втором случае она переносится на 0. он каким-то образом предполагает, что тип указателя имеет самый большой диапазон по сравнению с любыми целочисленными типами, тогда как со 128-битными целыми числами это не так.
Обратите внимание, что 128-битное целое число, вообще говоря, не является целочисленным типом, но, по крайней мере, gcc определяет его как в расширениях gcc:
от https://quuxplusone.github.io/blog/2019/02/28/is-int128-integral/
libstdc++ (в стандартном режиме, отличном от gnu++XX) оставляет is_integral_v<__int128> как false. Это имеет определенный смысл с точки зрения разработчика библиотеки, потому что __int128 не является одним из стандартных целочисленных типов, и, кроме того, если вы называете его целочисленным, то вы должны столкнуться с тем следствием, что intmax_t (который составляет 64 бита на каждый ЛПИ, который имеет значение) как бы лжет о том, что он «максимум».
но
В режиме -std=gnu++XX libstdc++ делает is_integral_v<__int128> равным true
Но void* не может содержать значение __uint128_t. У меня нет исходного значения, как вы видите во втором примере. Если бы значение было оригинальным, printf выдал бы «1».
Я отредактировал, чтобы уточнить. Похоже, что переинтерпретация приведения в этом случае небезопасна.
На самом деле эта часть выглядит как решение: (обратное преобразование в обратном направлении не гарантируется; один и тот же указатель может иметь несколько целочисленных представлений). Что немного сбивает с толку, я думаю, что это правильное мнение, что разработчики компилятора предполагают, что Тип указателя самый большой.
В основном я не понимаю, является ли это ошибкой компилятора, злоупотреблением спецификацией или следствием спецификации.
@CPPCPPCPPCPPCPPCPPCPPCPPCPPCPP Спецификация говорит, что любое целое число может быть преобразовано в указатель, так что это следствие спецификации.
@CPPCPPCPPCPPCPPCPPCPPCPPCPPCPP Это практически никогда не ошибка компилятора, это разумное предположение для языковых функций, которым уже несколько десятилетий. Совершенно новые функции C++ страдают от ошибок компилятора и библиотеки и даже от ошибок (кхм, недостатков) в стандарте. Но эти слепки в том виде, в каком вы их используете, уже давно устарели.
Ваша вторая цитата неприменима. __uint128_t не является "интегральным типом".
нет, не в целом, но в расширении gcc это так, см. мое редактирование (во всяком случае, хороший момент)
Заранее и хорошо изучите, что неопределенное поведение существует, и просто возможность скомпилировать/запустить что-то не означает, что это допустимый или безопасный код C++.
reinterpret_cast
просто сообщает компилятору: «Я думаю, что знаю, что делаю, так что позвольте мне это сделать», но четко документировано, какие приведения на самом деле допустимы, и компилятор не помешает вам сделать те, которые недействительны.