Является ли std :: memcpy между различными неопределенными типами типов с возможностью копирования?
Я использовал std::memcpy
чтобы долго обходить строгий псевдоним.
Например, досмотр float
, как это:
float f = ...;
uint32_t i;
static_assert(sizeof(f)==sizeof(i));
std::memcpy(&i, &f, sizeof(i));
// use i to extract f sign, exponent & significand
Однако, на этот раз, я проверил стандарт, я не нашел ничего, что подтвердило бы это. Все, что я нашел, это:
Для любого объекта (кроме потенциально перекрывающегося подобъекта) тривиально-скопируемого типа T, независимо от того, имеет ли объект допустимое значение типа T, базовые байты ([intro.memory]), составляющие объект, могут быть скопированы в массив char, unsigned char или std :: byte ([cstddef.syn]). 40 Если содержимое этого массива будет скопировано обратно в объект, объект впоследствии сохранит свое первоначальное значение. [ Пример:
#define N sizeof(T)
char buf[N];
T obj; // obj initialized to its original value
std::memcpy(buf, &obj, N); // between these two calls to std::memcpy, obj might be modified
std::memcpy(&obj, buf, N); // at this point, each subobject of obj of scalar type holds its original value
- конец примера]
и это:
Для любого тривиально скопируемого типа T, если два указателя на T указывают на различные T-объекты obj1 и obj2, где ни obj1, ни obj2 не являются потенциально перекрывающимся подобъектом, если базовые байты ([intro.memory]), составляющие obj1, копируются в obj2, 41 obj2 впоследствии будет иметь то же значение, что и obj1. [ Пример:
T* t1p;
T* t2p;
// provided that t2p points to an initialized object ...
std::memcpy(t1p, t2p, sizeof(T));
// at this point, every subobject of trivially copyable type in *t1p contains
// the same value as the corresponding subobject in *t2p
- конец примера]
Таким образом, допускается std::memcpy
ing float
to/from char[]
, а также std::memcpy
между теми же тривиальными типами.
Является ли мой первый пример (и связанный ответ) четко определенным? Или правильный способ проверки float
- это std::memcpy
it в unsigned char[]
buffer и с помощью shift
и or
для сборки uint32_t
из него?
Примечание: просмотр std::memcpy
гарантирует, что этот вопрос не может ответить. Насколько я знаю, я мог бы заменить std::memcpy
на простой цикл байтов, и вопрос будет таким же.
Ответы
Ответ 1
Стандарт может не сказать правильно, что это разрешено, но это почти наверняка должно быть, и, насколько мне известно, все реализации будут рассматривать это как определенное поведение.
Чтобы облегчить копирование в действительный объект char[N]
, байты, составляющие объект f
могут быть доступны, как если бы они были char[N]
. Эта часть, я считаю, не оспаривается.
Байты из char[N]
которые представляют значение uint32_t
могут быть скопированы в объект uint32_t
. Эта часть, я считаю, также не оспаривается.
Столь же неоспоримым, я считаю, является то, что, например, fwrite
может написать байты в одном прогоне программы, а fread
может прочитать их обратно в другом прогоне или даже в другой программе полностью.
Из-за этой последней части я считаю, что не имеет значения, откуда взялись байты, если они образуют допустимое представление некоторого объекта uint32_t
. Вы могли бы циклически memcmp
по всем значениям float
, используя memcmp
на каждом, пока вы не получите желаемое представление, которое, как вы знали, было бы идентично значению uint32_t
вы его интерпретируете. Вы могли бы даже сделать это в другой программе, программе, которую компилятор никогда не видел. Это было бы справедливо.
Если с точки зрения реализации ваш код неотличим от однозначно действующего кода, ваш код должен считаться действительным.
Ответ 2
Является ли мой первый пример (и связанный ответ) четко определенным?
Поведение не определено (если целевой тип не имеет представления ловушки † которые не разделяются по типу источника), но результирующее значение целого числа определяется реализацией. Стандарт не дает никаких гарантий относительно того, как представлены числа с плавающей запятой, поэтому нет способа извлечь мантиссы и т.д. Из целого числа переносимым способом, - сказал он, ограничившись использованием IEEE 754 с использованием систем, в наши дни это не ограничивает вас.
Проблемы переносимости:
- IEEE 754 не гарантируется C++
- Байтовская сущность float не гарантирует соответствия целочисленной конечности.
- (Системы с ловушкой представлениями †).
Вы можете использовать std::numeric_limits::is_iec559
для проверки правильности вашего предположения о представлении.
† Хотя, похоже, что uint32_t
не может иметь ловушки (см. Комментарии), поэтому вам не нужно беспокоиться. Используя uint32_t
, вы уже исключили переносимость для эзотерических систем - стандартным системам соответствия не требуется определять этот псевдоним.
Ответ 3
Ваш пример хорошо определен и не нарушает строгий псевдоним. std::memcpy
четко заявляет:
Копии count
байты от объекта, на который указывает src, на объект, на который указывает dest. Оба объекта интерпретируются как массивы unsigned char
.
Стандарт позволяет сглаживать любой тип с помощью (signed/unsigned) char*
или std::byte
(signed/unsigned) char*
и поэтому ваш пример не отображает UB. Если результирующее целое имеет любое значение, это еще один вопрос.
use я to extract f sign, exponent & significand
Это, однако, не гарантируется стандартом, так как значение float
определено реализацией (в случае IEEE 754 оно будет работать, хотя).