Существует три типа поведения стандартов, которые вас интересуют.
1 / Определенное поведение . Это будет работать на всех соответствующих реализациях. Используйте это свободно.
2 / Поведение, определяемое реализацией . Как уже говорилось, это зависит от реализации, но, по крайней мере, все еще определено. Реализации должны документировать, что они делают в этих случаях. Используйте это, если вас не волнует переносимость.
3 / Неопределенное поведение . Все может случиться. И мы имеем в виду что-нибудь , вплоть до того, что весь ваш компьютер рухнул в обнаженную единицу и поглотил себя, вас и большую часть ваших коллег по работе. Никогда не используйте это. Когда-либо! Шутки в сторону! Не заставляй меня приходить туда.
Копирование более 4 символов и нулевого байта в char[5]
является неопределенным поведением.
Серьезно, не имеет значения, почему ваша программа падает с 14 символами, но не с 13, вы почти наверняка перезаписываете некоторую информацию о сбое в стеке, и ваша программа, скорее всего, все равно будет давать неверные результаты. На самом деле, сбой лучше, так как, по крайней мере, он останавливает вас, полагаясь на возможно плохие последствия.
Увеличьте размер массива до более подходящего (char[14]
в данном случае с доступной информацией) или используйте другую структуру данных, которая может справиться.
Обновление:
Поскольку вы, похоже, очень заинтересованы в том, чтобы выяснить, почему дополнительные 7 символов не вызывают проблем, а 8 символов вызывают, давайте рассмотрим возможный макет стека при вводе main()
. Я говорю «возможно», поскольку фактическое расположение зависит от соглашения о вызовах, которое использует ваш компилятор. Поскольку код запуска C вызывает main()
с argc
и argv
, стек в начале main()
после выделения места для char[5]
может выглядеть следующим образом:
+------------------------------------+
| C start-up code return address (4) |
| argc (4) |
| argv (4) |
| x = char[5] (5) |
+------------------------------------+
Когда вы пишете байты Hello1234567\0
с:
strcpy (x, "Hello1234567");
до x
, он перезаписывает argc
и argv
, но по возвращении из main()
ничего страшного. В частности, Hello
заполняет x
, 1234
заполняет argv
и 567\0
заполняет argc
. Если вы на самом деле не пытаетесь использовать argc
и / или argv
после этого, все будет в порядке:
+------------------------------------+ Overwrites with:
| C start-up code return address (4) |
| argc (4) | '567<NUL>'
| argv (4) | '1234'
| x = char[5] (5) | 'Hello'
+------------------------------------+
Однако, если вы пишете Hello12345678\0
(обратите внимание на дополнительные «8») в x
, он перезаписывает argc
и argv
, а также один байт адреса возврата, так что когда main()
пытается вернуться к стартовому коду C, вместо этого он отправляется в сказочную страну:
+------------------------------------+ Overwrites with:
| C start-up code return address (4) | '<NUL>'
| argc (4) | '5678'
| argv (4) | '1234'
| x = char[5] (5) | 'Hello'
+------------------------------------+
Опять же, это полностью зависит от соглашения о вызовах вашего компилятора. Вполне возможно, что другой компилятор всегда будет разбивать массивы на кратные 4 байта, и код не потерпит неудачу, пока вы не напишите еще три символа. Даже один и тот же компилятор может по-разному распределять переменные в кадре стека, чтобы обеспечить выравнивание.
Вот что они имеют в виду под неопределенным: вы не не знаете , что произойдет.