Почему gcc разрешает инициализацию массива char с строковым литералом, большим, чем массив?
int main()
{
char a[7] = "Network";
return 0;
}
A строковый литерал в C завершается внутри с символом nul. Таким образом, приведенный выше код должен давать ошибку компиляции, так как фактическая длина строкового литерала Network
равна 8 и не может быть помещена в массив char[7]
.
Однако gcc (даже с -Wall
) на Ubuntu компилирует этот код без каких-либо ошибок или предупреждений.
Почему gcc разрешает это, а не помечать его как ошибку компиляции?
gcc только дает предупреждение (все еще нет ошибки!), когда размер массива char меньше, чем строковый литерал. Например, он предупреждает:
char a[6] = "Network";
[Related] Visual С++ 2012 дает ошибку компиляции для char a[7]
:
1>d:\main.cpp(3): error C2117: 'a' : array bounds overflow
1> d:\main.cpp(3) : see declaration of 'a'
Ответы
Ответ 1
Инициализация массива char строковым литералом, который больше, чем на C, но он неправильный в С++. Это объясняет разницу в поведении между gcc и VС++.
Вы не получите ошибки, если вы скомпилировали то же, что и файл C с VС++. И вы получите ошибку, если вы скомпилировали ее как файл С++ с g++.
В стандарте C говорится:
Массив типа символа может быть инициализирован символьной строкой буквальный или UTF-8 строковый литерал, необязательно заключенный в фигурные скобки. Последовательные байты строкового литерала (включая завершающий нуль символ, если есть место или массив неизвестного размера) инициализируйте элементы массива.
[...]
ПРИМЕР 8
Объявление
char s[] = "abc", t[3] = "abc";
определяет '' plain char объекты массива s
и t
, элементы которых инициализируются с символьными строковыми литералами. Эта декларация идентична
char s[] = { 'a', 'b', 'c', '\0' },
t[] = { 'a', 'b', 'c' };
(раздел 6.7.9 стандарт проекта C11, фактическая формулировка в окончательном стандарте может отличаться.)
Это означает, что он идеально подходит для удаления символа завершения, если в массиве нет места для него. Это может быть неожиданно, но это именно то, как должен работать язык, и (по крайней мере, мне) хорошо известную функцию.
Напротив, в стандарте С++ говорится:
Здесь не должно быть больше инициализаторов, чем элементов массива.
Пример:
char cv[4] = "asdf"; // error
плохо сформировалось, так как не существует пространства для подразумеваемого конечного "\ 0".
(8.5.2 проекта С++ 2011 n3242.)
Ответ 2
Предпочитаемый способ объявления строкового литерала обычно:
char a[] = "Network";
printf("size of a: %d\n", sizeof a); // The compiler 'knows' the size of a.
// this prints '8'
Пусть компилятор это выяснит. Это громоздко, чтобы вручную указать размер массива и сохранить его в синхронизации с фактической длиной строки...
Итак, я думаю, что GCC на самом деле не беспокоится ни о чем больше, чем о предупреждении.
Ответ 3
В первые дни C и Unix память и диск были небольшими, поэтому не хранить байт NUL в конце строки был фактически методом, который использовался. Если строковая переменная имеет длину семь символов, вы можете сохранить в ней семисимвольную строку, и поскольку семь были максимальной длиной, вы знали, что строка закончилась там, даже без символа терминатора. Вот почему strncpy работает так, как он делает.
Ответ 4
В то время как развернуть ответ объясняет, почему gcc
не предупреждает об этом, он не говорит, что вы можете с этим сделать.
gcc
-Wc++-compat
параметр предупреждения обнаружит эту проблему с сообщением:
foo.c: In function ‘main’:
foo.c:3:17: warning: initializer-string for array chars is too long for C++ [-Wc++-compat]
Это единственный параметр, который заставит gcc
предупредить об этой проблеме. Вы можете написать короткий script, чтобы быстро вывести параметры предупреждения из справочной страницы gcc
, попытаться выполнить компиляцию с каждым и посмотреть, не жалуется ли он.
$ time for F in $(man gcc | grep -o -- '-W[^= ]*')
do if gcc -c "${F}" foo.c |& grep :3 >& /dev/null; then
echo "${F}"; gcc -c "${F}" foo.c
fi
done
man gcc | grep -o -- '-W[^= ]*')
man gcc | grep -o -- '-W[^= ]*'
-Wall
foo.c: In function ‘main’:
foo.c:3:10: warning: unused variable ‘a’ [-Wunused-variable]
-Wc++-compat
foo.c: In function ‘main’:
foo.c:3:17: warning: initializer-string for array chars is too long for C++ [-Wc++-compat]
-Wunused
foo.c: In function ‘main’:
foo.c:3:10: warning: unused variable ‘a’ [-Wunused-variable]
-Wunused-variable
foo.c: In function ‘main’:
foo.c:3:10: warning: unused variable ‘a’ [-Wunused-variable]
-Wtraditional
foo.c: In function ‘main’:
foo.c:3:5: warning: traditional C rejects automatic aggregate initialization [-Wtraditional]
-Wall
foo.c: In function ‘main’:
foo.c:3:10: warning: unused variable ‘a’ [-Wunused-variable]
-Wall
foo.c: In function ‘main’:
foo.c:3:10: warning: unused variable ‘a’ [-Wunused-variable]
-Wunused-variable
foo.c: In function ‘main’:
foo.c:3:10: warning: unused variable ‘a’ [-Wunused-variable]
-Wunused
foo.c: In function ‘main’:
foo.c:3:10: warning: unused variable ‘a’ [-Wunused-variable]
-Wunused
foo.c: In function ‘main’:
foo.c:3:10: warning: unused variable ‘a’ [-Wunused-variable]
-Wall
foo.c: In function ‘main’:
foo.c:3:10: warning: unused variable ‘a’ [-Wunused-variable]
-Wall
foo.c: In function ‘main’:
foo.c:3:10: warning: unused variable ‘a’ [-Wunused-variable]
-Wall
foo.c: In function ‘main’:
foo.c:3:10: warning: unused variable ‘a’ [-Wunused-variable]
-Wunused
foo.c: In function ‘main’:
foo.c:3:10: warning: unused variable ‘a’ [-Wunused-variable]
-Wunused-variable
foo.c: In function ‘main’:
foo.c:3:10: warning: unused variable ‘a’ [-Wunused-variable]
-Wunused
foo.c: In function ‘main’:
foo.c:3:10: warning: unused variable ‘a’ [-Wunused-variable]
-Wunused
foo.c: In function ‘main’:
foo.c:3:10: warning: unused variable ‘a’ [-Wunused-variable]
-Wunused
foo.c: In function ‘main’:
foo.c:3:10: warning: unused variable ‘a’ [-Wunused-variable]
-Wall
foo.c: In function ‘main’:
foo.c:3:10: warning: unused variable ‘a’ [-Wunused-variable]
-Wall
foo.c: In function ‘main’:
foo.c:3:10: warning: unused variable ‘a’ [-Wunused-variable]
-Wall
foo.c: In function ‘main’:
foo.c:3:10: warning: unused variable ‘a’ [-Wunused-variable]
-Wall
foo.c: In function ‘main’:
foo.c:3:10: warning: unused variable ‘a’ [-Wunused-variable]
-Wtraditional
foo.c: In function ‘main’:
foo.c:3:5: warning: traditional C rejects automatic aggregate initialization [-Wtraditional]
-Wtraditional
foo.c: In function ‘main’:
foo.c:3:5: warning: traditional C rejects automatic aggregate initialization [-Wtraditional]
-Wc++-compat
foo.c: In function ‘main’:
foo.c:3:17: warning: initializer-string for array chars is too long for C++ [-Wc++-compat]
-Wall
foo.c: In function ‘main’:
foo.c:3:10: warning: unused variable ‘a’ [-Wunused-variable]
-Wtraditional
foo.c: In function ‘main’:
foo.c:3:5: warning: traditional C rejects automatic aggregate initialization [-Wtraditional]
-Wall
foo.c: In function ‘main’:
foo.c:3:10: warning: unused variable ‘a’ [-Wunused-variable]
-Wall
foo.c: In function ‘main’:
foo.c:3:10: warning: unused variable ‘a’ [-Wunused-variable]
-Wall
foo.c: In function ‘main’:
foo.c:3:10: warning: unused variable ‘a’ [-Wunused-variable]
-Wall
foo.c: In function ‘main’:
foo.c:3:10: warning: unused variable ‘a’ [-Wunused-variable]
-Wall
foo.c: In function ‘main’:
foo.c:3:10: warning: unused variable ‘a’ [-Wunused-variable]
-Wtraditional
foo.c: In function ‘main’:
foo.c:3:5: warning: traditional C rejects automatic aggregate initialization [-Wtraditional]
real 0m26.399s
user 0m5.128s
sys 0m15.329s
В общем, lint -подобный инструмент, такой как splint
предупредит вас обо всех возможных проблемах. В этом случае он скажет:
foo.c:3:17: String literal with 8 characters is assigned to char [7] (no room
for null terminator): "Network"
A string literal is assigned to a char array that is not big enough to hold
the null terminator. (Use -stringliteralnoroom to inhibit warning)
foo.c:3:10: Variable a declared but not used