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 

最後から 2 行目に余分な 1 があることに注意してください。また、memset の後に印刷命令のコメントを外すと、その 1 が消えます。これは、次の 2 つの事実に依存しているようです。

  1. long double はパディングされます。つまり、sizeof(long double) = 16 ですが、10 バイトしか使用されません。
  2. memset への呼び出しが最適化される可能性があります
  3. long double のパディング ビットは予告なしに変更される可能性があります。つまり、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 には、「構造オブジェクト内の位置合わせの目的でパディングとして使用される「穴」の内容は不確定です」と記載されています。前のテキストは未指定のバイトを参照していますが、それは単にバイトがトラップ表現を持たないためです。

これは厳密なエイリアシング違反ですか?

コードに厳密なエイリアシング違反は見られません。

于 2013-09-07T01:56:12.840 に答える
-2

ここで未定義のものや未指定のものは見当たりません (2 つの非常に異なるもの)。はい、memset()呼び出しは最適化されています。私のマシン (i86-32) では、long double は 12 バイトで、構造体とスタックで 16 にパディングされます。あなたのマシンでは、 が 16 を返すので、それらは明らかに完全な 16 バイトsizeof(long double)です。どちらの「printme」出力も、適切な IEEE 128 ビット浮動小数点形式に似ていないため、printme()関数にはここに示されていない他のバグがあると思われます。

于 2013-09-07T02:31:27.747 に答える