通过并集和位移读取双平台字节序,是否安全?

我所看到的所有从缓冲区到平台字节序读取已知字节序的两倍的例子包括检测当前平台的字节顺序并在必要时执行字节交换。

另一方面,除了使用位移的整数( 一个这样的例子 )之外,我已经看到了另一种做同样事情的方法。

这让我觉得有可能使用union和bitshift技术从缓冲区中读取双精度(和浮点数),并且快速测试实现似乎有效(至少在x86_64上使用clang):

#include  #include  #include  double read_double(char * buffer, bool le) { union { double d; uint64_t i; } data; data.i = 0; int off = le ? 0 : 7; int add = le ? 1 : -1; for (int i = 0; i < 8; i++) { data.i |= ((uint64_t)(buffer[off] & 0xFF) << (i * 8)); off += add; } return data.d; } int main() { char buffer_le[] = {0x6E, 0x86, 0x1B, 0xF0, 0xF9, 0x21, 0x09, 0x40}; printf("%f\n", read_double(buffer_le, true)); // 3.141590 char buffer_be[] = {0x40, 0x09, 0x21, 0xF9, 0xF0, 0x1B, 0x86, 0x6E}; printf("%f\n", read_double(buffer_be, false)); // 3.141590 return 0; } 

我的问题是,这是一种安全的方法吗? 或者这里涉及到未定义的行为? 或者,如果这个和字节交换方法都涉及未定义的行为,那么一个比另一个更安全吗?

通过联盟重新解释

通过移位和ORing字节构造uint64_t值当然是由C标准支持的。 (由于需要确保左操作数是正确的大小和类型以避免溢出和移位宽度问题,因此在移位时存在一些危险,但问题中的代码在移位之前正确转换为uint64_t 。)然后问题仍然存在代码是C标准是否允许通过联合重新解释。 答案是肯定的。

C 6.5.2.3 3说:

后缀表达式后跟 运算符和标识符指定结构或联合对象的成员。 该值是指定成员的值, 99)

并注释99说:

如果用于读取union对象内容的成员与上次用于在对象中存储值的成员不同,则将值的对象表示的相应部分重新解释为新类型中的对象表示forms在6.2.6中描述(一个有时被称为“类型双关语”的过程)……

当然,这种重新解释依赖于C实现中使用的对象表示。 值得注意的是, double必须使用预期的格式,匹配从输入流中读取的字节。

修改对象的字节

C. C 2018 6.5 7允许通过修改其字节来修改对象(如使用指向unsigned char的指针):

对象的存储值只能由具有以下类型之一的左值表达式访问:[各种类型的列表]或字符类型。

虽然其中一条评论声明你可以“访问”而不是以这种方式“修改”对象的字节(显然将“访问”解释为仅指读,而不是写),C 2018 3.1将“访问”定义为:

读取或修改对象的值。

因此,允许一个人通过字符类型读取或写入对象的字节。

通过并集和位移读取双平台字节序,是否安全?

这种事情在处理来自程序外部的数据时才有意义(例如来自文件或网络的数据); 您对数据的严格格式(在文件格式规范或网络协议的规范中定义)可能与C使用的格式无关,可能与CPU使用无关,可能不是IEEE 754格式无论是。

另一方面,C根本不提供任何保证。 举一个简单的例子,编译器使用float BCD格式是完全合法的,其中0x12345e78 = 1.2345 * 10**78 ,即使CPU本身恰好支持“IEEE 754”。

结果是你从程序外部得到“无论规范格式”,你将它转换成一个不同的“无论编译器是什么样的格式”,以便在程序中使用; 你所做的每一个假设(包括sizeof(double) )都可能是假的。