【问题标题】:Reading double to platform endianness with union and bit shift, is it safe?使用联合和位移读取双至平台字节序,安全吗?
【发布时间】:2019-03-08 22:52:22
【问题描述】:

我见过的从缓冲区读取双倍已知字节序到平台字节序的所有示例都涉及检测当前平台的字节序并在必要时执行字节交换。

另一方面,除了使用位移位的整数 (one such example),我还看到了另一种做同样事情的方法。

这让我想到可以使用联合和位移技术从缓冲区读取双精度数(和浮点数),并且快速测试实现似乎有效(至少在 x86_64 上使用 clang):

#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>

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;
}

我的问题是,这是一种安全的方法吗?或者这里是否涉及未定义的行为?或者,如果 this 和 byte-swap 方法都涉及未定义的行为,那么一个比另一个更安全吗?

【问题讨论】:

  • @EugeneSh。 memcpy is the way to avoid strict aliasing 你是否有一个有效的替身是另一回事,但我认为标准不能保证这一点。
  • @EugeneSh.:C 2018 6.5.2.3 3 允许通过联合重新解释,脚注 99 明确确认这是文本的预期含义。
  • @EugeneSh.: C 2018,C 2011,C 1999中的子句和段落是一样的。注释在C 2018中是99,在C 2011中是95。C 1999没有注释.
  • @EugeneSh.:通过字符类型修改对象的字节不会违反别名规则。根据 C 2018 6.5 7,“对象的存储值只能由……[各种类型]或字符类型访问。”根据 3.1 1,“访问”意味着“读取或修改对象的值”。
  • @EugeneSh.:当人们通过structure.member 表达式读取联合的成员时,我们正在通过自己的类型读取作为该成员的对象,这当然与自身兼容。所以它不违反别名规则。如果有这样的规则但没有,它可能会违反关于访问最后写入成员以外的成员的规则。

标签: c decode endianness undefined-behavior decoding


【解决方案1】:

通过联合重新解释

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

C 6.5.2.3 3 说:

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

注释 99 说:

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

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

修改对象的字节

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

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

尽管其中一个 cmets 声明您可以以这种方式“访问”但不能“修改”对象的字节(显然将“访问”解释为仅表示读取,而不是写入),但 C 2018 3.1 将“访问”定义为:

读取或修改对象的值。

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

【讨论】:

    【解决方案2】:

    通过联合和位移读取双至平台字节序,安全吗?

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

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

    结果是您从程序外部获得了“规范所说的格式”,并且您将其转换为不同的“编译器感觉格式”以在程序内部使用;你所做的每一个假设(包括sizeof(double))都可能是错误的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-03-19
      • 2021-09-13
      • 1970-01-01
      • 2013-09-22
      • 1970-01-01
      • 1970-01-01
      • 2013-05-27
      • 1970-01-01
      相关资源
      最近更新 更多