3

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 与 -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 doubles 的填充位可能会在没有通知的情况下发生变化,即 value1 和 value2 上的浮点运算似乎会扰乱填充位。

我正在编译-std=c99 -Wall -Wextra -Wpedantic并且没有收到任何警告,所以我不确定这是一个严格的混叠违规情况(但很可能是)。通过-fno-strict-aliasing并没有改变任何事情。

上下文是在此处描述的 HDF5 库中发现的错误。HDF5 在计算浮点类型的本机位表示方面做了一些调整,但如果填充位不保持为零,它就会感到困惑。

所以:

  1. 这是未定义的行为吗?
  2. 这是一个严格的混叠违规吗?

谢谢。

编辑:这是 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");
}
4

3 回答 3

2

虽然 C 标准允许操作破坏填充位,但我认为这不是您的系统上发生的情况。相反,它们从一开始就没有被初始化,而 GCC 只是优化了memsetat -O1,因为该对象随后被覆盖。这可能会被-fno-builtin-memset.

于 2013-09-07T02:28:05.377 回答
0

这是未定义的行为吗?

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

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

这是一个严格的混叠违规吗?

我在您的代码中没有看到任何严格的别名违规。

于 2013-09-07T01:56:12.840 回答
-2

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

于 2013-09-07T02:31:27.747 回答