Подтвердить что ты не робот

Почему 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'
4b9b3361

Ответ 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