Почему неинициализированы, а не вне пределов?

В коде ниже, почему 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 видит вне пределов того, что это такое.

Ответ 1

Я считаю, что это может быть здесь: в первом коде GCC замечает, что вам не нужен весь массив символов вообще, просто b[9], поэтому он может заменить код на

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

Теперь это полностью законное преобразование, потому что, поскольку массив был доступен за пределами, поведение полностью не определено. Только в последнем случае он замечает, что эта переменная, которая является заменой b[9], неинициализирована и выдает сообщение диагностики.

Почему я этому верю? Потому что, если я добавлю только любой код, который будет ссылаться на адрес массива в памяти, например printf("%p\n", &b[8]); в любом месте массив теперь полностью реализуется в памяти, а компилятор будет диагностировать индекс массива выше границ массива.


Еще более интересным является то, что GCC вообще не диагностирует доступ к ограничениям, если оптимизация не включена. Это снова подсказывает, что всякий раз, когда вы пишете новую программу, вы должны скомпилировать ее с включенными оптимизациями, чтобы сделать ошибки более заметными, а не скрывать их в режиме отладки;)

Ответ 2

Поведение при чтении b[9] или b[10] не определено.

Ваш компилятор выдает предупреждение (оно не обязательно), хотя текст предупреждения немного вводит в заблуждение, но не является технически неправильным. По-моему, это довольно умно. (Компилятор AC не обязан выдавать диагностику для доступа за пределы доступа.)

Что касается &b[9], компилятору не разрешается разыгрывать это, и он должен оценивать его как b + 9. Вы можете установить указатель один за концом массива. Поведение установки указателя на &b[10] не определено.

Ответ 3

Некоторые дополнительные экспериментальные результаты.


Использование char b[9] вместо char b[] не имеет значения, gcc все равно предупреждает об этом с char b[9].

Интересно, что инициализация однопроходного элемента через "следующий" член в struct 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

Ответ 4

Когда вы компилируете код с помощью -O2, тривиальность примера делает эту переменную оптимизированной. Таким образом, предупреждение на 100% правильное