C++ 不同的可复制类型之间的std::memcpy是否未定义行为?

C++ 不同的可复制类型之间的std::memcpy是否未定义行为?,c++,language-lawyer,c++17,undefined-behavior,strict-aliasing,C++,Language Lawyer,C++17,Undefined Behavior,Strict Aliasing,很长一段时间以来,我一直在使用std::memcpy来规避严格的别名 例如,检查一个浮动,如: 然而,这一次,我检查了标准,我没有发现任何验证这一点的东西。我所发现的是: 对于普通可复制类型T的任何对象(可能重叠的子对象除外),无论该对象是否持有类型T的有效值,构成该对象的底层字节([intro.memory])都可以复制到char、unsigned char或std数组中​::​字节([cstddef.syn]).40如果将该数组的内容复制回对象,则该对象随后应保持其原始值。[ 例如: #de

很长一段时间以来,我一直在使用
std::memcpy
来规避严格的别名

例如,检查一个
浮动
,如:

然而,这一次,我检查了标准,我没有发现任何验证这一点的东西。我所发现的是:

对于普通可复制类型T的任何对象(可能重叠的子对象除外),无论该对象是否持有类型T的有效值,构成该对象的底层字节([intro.memory])都可以复制到char、unsigned char或std数组中​::​字节([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* 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
- 结束示例 ]

以及:

对于任何普通的可复制类型T,如果指向T的两个指针指向不同的T对象obj1和obj2,其中obj1和obj2都不是潜在的重叠子对象,如果构成obj1的基础字节([intro.memory])被复制到obj2中,则41 obj2随后应与obj1保持相同的值[ 例如:

#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* 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 a
float
to/from
char[]
是允许的,并且
std::memcpy
在相同的普通类型之间也是允许的

我的第一个例子(以及链接的答案)定义得好吗?或者检查
浮点的正确方法是
std::memcpy
将其放入
无符号字符[]
缓冲区,然后使用
shift
s和
s从中构建
uint32\t


注意:查看
std::memcpy
的保证可能无法回答这个问题。据我所知,我可以用一个简单的字节复制循环来替换
std::memcpy
,问题也一样

我的第一个例子(以及链接的答案)定义得好吗

行为不是未定义的(除非目标类型具有源类型不共享的陷阱表示†),但整数的结果值是由实现定义的。标准对浮点数的表示方式没有任何保证,因此无法以可移植的方式从整数中提取尾数等-也就是说,将自己限制在使用系统的IEEE 754并不会对您造成多大限制

便携性问题:

  • IEEE 754不受C++
  • 浮点的字节结束度不保证与整数结束度匹配
  • (具有陷阱表示的系统†)
您可以使用
std::numeric\u limits::is\u iec559
验证您关于表示的假设是否正确


†尽管如此,
uint32\u t
似乎不能有陷阱(见注释)因此,您不必担心。通过使用
uint32\t
,您已经排除了对深奥系统的可移植性-不需要符合标准的系统来定义别名。

标准可能没有正确地说明这是允许的,但几乎可以肯定的是,据我所知,所有实现都是我将把这视为明确的行为

为了便于复制到实际的
char[N]
对象中,组成
f
对象的字节可以像访问
char[N]
一样进行访问。我相信这一部分没有争议

表示
uint32\u t
值的
char[N]
中的字节可以复制到
uint32\u t
对象中。我相信这一部分也没有争议

我认为,同样无可争议的是,例如,
fwrite
可能在程序的一次运行中写入了字节,而
fread
可能在另一次运行中,甚至在另一个程序中完全读取了字节

由于最后一部分的原因,我相信字节从哪里来并不重要,只要它们形成了一些
uint32\t
对象的有效表示形式。您可以循环使用所有
float
值,在每个值上使用
memcmp
,直到得到您想要的表示形式,您知道这些表示形式与此相同您将其解释为的
uint32\t
值。您甚至可以在另一个程序中这样做,一个编译器从未见过的程序。这是有效的


如果从实现的角度来看,您的代码与明确有效的代码无法区分,那么您的代码必须被视为有效的。

您的示例定义良好,并且没有打破严格的别名。
std::memcpy
明确指出:

将src指向的对象的
count
字节复制到该对象 由dest指向。这两个对象都被重新解释为
无符号字符

该标准允许通过
(有符号/无符号)char*
std::byte
对任何类型进行别名,因此您的示例不会显示UB。但是,如果生成的整数具有任何值,则是另一个问题


使用i提取f的符号、指数和有效位


然而,标准并不能保证这一点,因为
float
的值是实现定义的(在IEEE 754的情况下,它可以工作)。

只要它们的大小相同,就不会有问题。但是,如果您只需要将
f
解释为
uint32\u t
,您可以编写
(uint32\u t&)f
。它将解释
float
的内存位置,就好像它是
uint32\t
@NO\u NAME。我的实验表明,你的建议违反了严格的别名规则。Related:Related:@NO\u NAME它仍然违反了严格的别名规则。有效的语法并不意味着有效的操作。就像英语句子是gr语法上是正确的,但毫无意义。将涉及的单个步骤分开,并将每个步骤确定为无争议的,这让事情变得清楚了。OP观察到的是