访问长双位表示

时间:2013-09-07 01:51:45

标签: c strict-aliasing long-double

TLDR;以下代码是否调用未定义(或未指定)的行为?

#include <stdio.h>
#include <string.h>

void printme(void *c, size_t n)
{
  /* print n bytes in binary */
}

int main() {
  long double value1 = 0;
  long double value2 = 0;

  memset( (void*) &value1, 0x00, sizeof(long double));
  memset( (void*) &value2, 0x00, sizeof(long double));

  /* printf("value1: "); */
  /* printme(&value1, sizeof(long double)); */
  /* printf("value2: "); */
  /* printme(&value2, sizeof(long double)); */

  value1 = 0.0;
  value2 = 1.0;

  printf("value1: %Lf\n", value1);
  printme(&value1, sizeof(long double));
  printf("value2: %Lf\n", value2);
  printme(&value2, sizeof(long double));

  return 0;
}

在我的x86-64机器上,输出取决于传递给编译器的特定优化标志(gcc-4.8.0,-O0 vs -O1)。

使用-O0,我得到了

value1: 0.000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 
value2: 1.000000
00000000 00000000 00000000 00000000 00000000 00000000 00111111 11111111
10000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000

使用-O1时,我得到了

value1: 0.000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 
value2: 1.000000
00000000 00000000 00000000 00000000 00000000 01000000 00111111 11111111
10000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 

请注意倒数第二行的额外1。此外,在memset之后取消注释打印指令使得1消失。这似乎依赖于两个事实:

  1. long double被填充,即sizeof(long double)= 16但只使用10个字节。
  2. 对memset的调用可能会被优化掉
  3. long double的填充位可能会在没有通知的情况下发生变化,即value1和value2上的浮点运算似乎会对填充位进行加扰。
  4. 我正在使用-std=c99 -Wall -Wextra -Wpedantic进行编译并且没有得到任何警告,所以我不确定这是否是严格别名违规的情况(但很可能是这样)。传递-fno-strict-aliasing不会改变任何事情。

    上下文是在here描述的HDF5库中发现的错误。 HDF5做了一些调整以找出浮点类型的本机位表示,但如果填充位不保持为零,则会混淆。

    所以:

    1. 这是未定义的行为吗?
    2. 这是严格的别名违规吗?
    3. 感谢。

      编辑:这是printme的代码。我承认我刚刚从某个地方切割和粘贴而没有过多关注它。如果故障发生在这里,我会穿着裤子走到桌边。

      void printme(void *c, size_t n)
      {
        unsigned char *t = c;
        if (c == NULL)
          return;
        while (n > 0) {
          int q;
          --n;
          for(q = 0x80; q; q >>= 1) 
            printf("%x", !!(t[n] & q));
          printf(" ");
        }
        printf("\n");
      }
      

3 个答案:

答案 0 :(得分:2)

虽然C标准允许操作破坏填充位,但我不认为这是您系统上发生的事情。相反,它们从未被初始化,并且GCC只是优化memset -O1,因为该对象随后被覆盖。这可能会被-fno-builtin-memset抑制。

答案 1 :(得分:0)

  

这是未定义的行为吗?

是。填充位是不确定的(*)。访问不确定的内存might as well be undefined behavior(它在C90中是未定义的行为,而一些C99编译器将其视为未定义的行为。此外,C99基本原理说访问不确定的内存意图是未定义的行为。但C99标准本身并未说明很明显,它只是暗示陷阱表示,并且可能给人的印象是,如果一个人知道一个人没有陷阱表示,那么就可以从不确定的记忆中获得未指定的值。 long double的填充部分至少是未指定的。

(*)C99的脚注271说“为了在结构对象中对齐而用作填充的''孔'的内容是不确定的。”前面的文本引用了未指定的字节,但这只是因为字节没有陷阱表示。

  

这是严格的别名违规吗?

我的代码中没有看到任何严格的别名冲突。

答案 2 :(得分:-2)

我在这里看不到任何未定义的东西,甚至没有指明(两个非常不同的东西)。是的,memset()来电已经过优化。在我的机器上(i86-32),long double是12个字节,在结构和堆栈中填充到16。在你的机器上,它们显然是16个字节,因为sizeof(long double)正在返回16.两个“printme”输出都不像正确的IEEE 128位浮点格式,所以我怀疑{{1}中还有其他错误此处未显示的函数。