Является ли 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 оно будет работать, хотя).