很长时间以来,我一直在使用std::memcpy
来规避严格别名。
例如,检查this之类的float
:
float f = ...;
uint32_t i;
static_assert(sizeof(f)==sizeof(i));
std::memcpy(&i, &f, sizeof(i));
// use i to extract f's sign, exponent & significand
但是,这一次,我检查了标准,但没有发现任何可验证此标准的东西。我发现的只是this:
对于平凡可复制的类型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
-示例]
和this:
对于任何平凡可复制的类型T,如果指向T的两个指针指向不同的T对象obj1和obj2,则如果组成obj1的基础字节([intro.memory])都不是可能重叠的子对象,则obj1和obj2都不是可能重叠的子对象复制到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
与float
进行char[]
的往返操作,也允许在相同的琐碎类型之间进行std::memcpy
的操作。
我的第一个示例(以及链接的答案)是否定义正确?或者检查float
的正确方法是将std::memcpy
放入unsigned char[]
缓冲区中,并使用shift
和or
s来构建{{1 }}?
注意:查看uint32_t
的保证可能无法回答这个问题。据我所知,我可以用一个简单的字节复制循环替换std::memcpy
,问题将是相同的。
答案 0 :(得分:21)
该标准可能无法正确地说明允许这样做,但是据我所知,所有实现都将其视为已定义的行为。
为了便于复制到实际的char[N]
对象中,可以像访问f
一样访问组成char[N]
对象的字节。我相信这部分没有争议。
来自char[N]
的代表uint32_t
值的字节可以复制到uint32_t
对象中。我相信这部分也没有争议。
我认为同样毫无争议的是fwrite
可能已经在程序的一次运行中写入了字节,而fread
可能已经在另一次运行甚至是另一程序中完全读取了字节。
由于最后一部分,我相信字节来自哪里都没有关系,只要它们形成某些uint32_t
对象的有效表示即可。您可以 遍历所有float
值,并在每个值上使用memcmp
,直到获得所需的表示形式为止,您知道该表示形式将与{{1} }您将其解释为。您甚至可以在另一个程序中做到这一点,这是编译器从未见过的程序。那本来是有效的。
如果从实现的角度来看,您的代码与明确有效的代码没有区别,则必须将您的代码视为有效。
答案 1 :(得分:18)
我的第一个示例(以及链接的答案)是否定义正确?
该行为不是未定义的(除非目标类型具有陷阱表示†,该陷阱表示不与源类型共享),但是整数的结果值是实现定义的。 Standard无法保证如何表示浮点数,因此无法以可移植的方式从整数中提取尾数等-也就是说,使用系统将自己限制为IEEE 754如今并没有太大限制。
可移植性问题:
您可以使用DB::rollback()
来验证关于表示的假设是否正确。
†尽管std::numeric_limits::is_iec559
似乎没有陷阱(请参见注释),所以您不必担心。通过使用uint32_t
,您已经排除了对深奥系统的可移植性-不需要使用符合标准的系统来定义该别名。
答案 2 :(得分:13)
您的示例定义明确,不会破坏严格的别名。 std::memcpy
明确指出:
从src指向的对象中复制
count
个字节到该对象 dest指出。 两个对象都重新解释为的数组unsigned char
。
该标准允许通过(signed/unsigned) char*
或std::byte
别名任何类型,因此您的示例不显示UB。如果结果整数具有任何值,则是另一个问题。
use i to extract f's sign, exponent & significand
但是,由于标准float
的值是实现定义的,因此不能保证此标准(但是对于IEEE 754,它可以工作)。