c cast and deference a pointer 严格别名

c cast and deference a pointer strict aliasing

http://blog.regehr.org/archives/1307 中,作者声称以下代码段具有未定义的行为:

unsigned long bogus_conversion(double d) {
  unsigned long *lp = (unsigned long *)&d;
  return *lp;
}

参数基于http://port70.net/~nsz/c/c11/n1570.html#6.5p7,指定了允许访问的情况。然而,在这个要点的脚注 (88) 中,它说这个列表仅用于检查别名目的,所以我认为这个片段很好,假设 sizeof(long) == sizeof(double).

我的问题是上面的片段是否被允许。

代码段有误,但不是因为别名。首先,有一个简单的规则表明,将指针引用到具有与其有效类型不同的类型的对象是错误的。这里的有效类型是double,所以有错误。 这种保护措施存在于标准中,因为 double 的位表示不能是 unsigned long 的有效表示,尽管现在这很奇特。

其次,从更实际的角度来看,doubleunsigned long 可能具有不同的对齐属性,以这种方式访问​​它可能会产生总线错误或只有 运行 时间惩罚。

一般来说,像这样的指针几乎总是错误的,没有定义的行为,是糟糕的风格,而且无论如何基本上都是无用的。在关于这些问题的争论中专注于 aliasing 是一个坏习惯,可能源于难以理解和可怕的 gcc 警告。

如果你真的想知道某种类型的位表示,"effective type"规则有一些例外。 C 标准明确定义了两种可移植的解决方案:

  • 使用 unsigned char* 并检查字节。
  • 使用包含两种类型的 union,将值存储在那里并使用另一种类型读取它。通过这种方式,您告诉编译器您想要一个可以被视为两种类型的对象。但是这里你不应该使用 unsigned long 作为目标类型,而是 uint64_t,因为你必须确保大小正是你认为的那样,并且没有陷阱表示。

为了说明这一点,这里有与问题中相同的功能,但具有定义的行为。

unsigned long valid_conversion(double d) {
  union {
    unsigned long ul;
    double d;
  } ub = { .d = d, };
  return ub.ul;
}

我的编译器(Debian 上的 gcc,没什么特别的)将它编译成 完全 与问题中的代码相同的汇编程序。只有你知道这个代码是可移植的。