web-dev-qa-db-ja.com

範囲外ではなく初期化されないのはなぜですか?

以下のコードでは、なぜb[9]範囲外ではなく初期化されていませんか?

#include <stdio.h>

int main(void)
{
    char b[] = {'N', 'i', 'c', 'e', ' ', 'y', 'o', 'u', '!'};
    printf("b[9] = %d\n", b[9]);

    return 0;
}

コンパイラー呼び出し:

% gcc -O2 -W -Wall -pedantic -c foo.c
foo.c: In function ‘main’:
foo.c:6:5: warning: ‘b[9]’ is used uninitialized in this function [-Wuninitialized]
     printf("b[9] = %d\n", b[9]);
% gcc --version
gcc (Ubuntu 5.4.0-6ubuntu1~16.04.6) 5.4.0 20160609
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

更新:これは奇妙です:

#include <stdio.h>

void foo(char *);

int main(void)
{
    char b[] = {'N', 'i', 'c', 'e', ' ', 'y', 'o', 'u', '!'};
    foo(&b[9]);
    foo(&b[10]);
    printf("b[9] = %d\n", b[9]);
    printf("b[10] = %d\n", b[10]);

    return 0;
}

これをコンパイルすると、予想される警告が表示されます。

% gcc -O2 -W -Wall -pedantic -c foo.c
foo.c: In function ‘main’:
foo.c:9:5: warning: array subscript is above array bounds [-Warray-bounds]
     foo(&b[10]);
     ^
foo.c:10:29: warning: array subscript is above array bounds [-Warray-bounds]
     printf("b[9] = %d\n", b[9]);
                             ^
foo.c:11:29: warning: array subscript is above array bounds [-Warray-bounds]
     printf("b[10] = %d\n", b[10]);

突然gccは、それが何であるかを範囲外に見ます。

45

私はこれがここにあると信じています:最初のコードでは、GCCはchar配列全体をまったく必要とせず、単に_b[9]_を必要とするので、コードを

_char b_9; // = ???
printf("b[9] = %d\n", b_9);
_

さて、これはcompletely正当な変換です。なぜなら、配列が範囲外でアクセスされたときの振る舞いは完全に未定義であるためです。後者のフェーズでのみ、_b[9]_の代わりであるこの変数が初期化されず、診断メッセージを発行することに気付きます。

なぜ私はこれを信じますか?なぜなら、justを追加する場合、コードはreferencememory、たとえばprintf("%p\n", &b[8]);どこでも、配列はメモリ内で完全に認識され、コンパイラは配列の添字が配列の境界を超えています


さらに興味深いのは、最適化が有効になっていない限り、GCCは範囲外アクセスを診断しないことです。これは、プログラムを新規プログラムを作成するときはいつでも、デバッグモードでバグを非表示にするのではなく、バグを目立つように最適化してコンパイルする必要があることを示唆しています。

57
Antti Haapala

b[9]またはb[10]を読み取るときの動作は未定義です。

コンパイラーは警告を発行しています(警告する必要はありません)。ただし、警告テキストは誤解を招くかもしれませんが、技術的には正しくありません。私の意見では、それはかなり賢いです。 (Cコンパイラは、範囲外アクセスの診断を発行するためにnotが必要です。)

&b[9]に関しては、コンパイラはnotを間接参照することを許可されており、b + 9として評価する必要があります。配列の末尾の1つ先にポインターを設定できます。 &b[10]へのポインターを設定する動作は、未定義です。

17
Bathsheba

追加の実験結果。


char b[9]の代わりにchar b[]を使用しても違いはないように見えますが、gccはchar b[9]でも同じことを警告します。

興味深いことに、structの「次の」メンバーを介して1回渡された要素を初期化すると、1)「初期化されていない」警告が静かになり、2)配列外へのアクセスについて警告されません。

#include <stdio.h>

typedef struct {
  char c[9];
  char d[9];
} TwoNines;

int main(void) {
  char b[9] = { 'N', 'i', 'c', 'e', ' ', 'y', 'o', 'u', '!' };
  printf("b[] size %zu\n", sizeof b);
  printf("b[9] = %d\n", b[9]);   // 'b[9]' is used uninitialized in this function [-Wuninitialized]

  TwoNines e = { { 'N', 'i', 'c', 'e', ' ', 'y', 'o', 'u', '!' }, //
                 { 'N', 'i', 'c', 'e', ' ', 'y', 'o', 'u', '!' } };

  printf("e size %zu\n", sizeof e);
  printf("e.c[9] = %d\n", e.c[9]);   // No warning.

  return 0;
}

出力

b[] size 9
b[9] = 0
e size 18    // With 18, we know `e` is packed.
e.c[9] = 78  // 'N'

ノート:
gcc -std = c11 -O3 -g3 -pedantic -Wall -Wextra -Wconversion -c -fmessage-length = 0 -v -MMD -MP ...
gcc/gcc-7.3.0-2.i686

1
chux