Почему неинициализированы, а не вне пределов?
В коде ниже, почему 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% правильное