Вопрос

рассмотрим приведенную ниже программу

    char str[5];
    strcpy(str,"Hello12345678");
    printf("%s",str);

При запуске эта программа выдает ошибку сегментации.

Но когда strcpy заменяется на following, программа работает нормально.

strcpy(str,"Hello1234567");

Итак, вопрос в том, что он должен аварийно завершиться при попытке скопировать в str любую другую строку длиной более 5 символов.

Итак, почему это не сбой для "Hello1234567", а сбой только для "Hello12345678", т. Е. строки длиной 13 или более 13.

Эта программа была запущена на 32 - разрядной машине .

Это было полезно?

Решение

Существует три типа стандартного поведения, которые должны вас заинтересовать.

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 байтам, и код не давал бы сбоя до тех пор, пока вы не написали бы еще три символа.Даже один и тот же компилятор может по-разному распределять переменные во фрейме стека, чтобы обеспечить соответствие.

Вот что они подразумевают под неопределенным:ты этого не делаешь знать что должно произойти.

Другие советы

Вы копируете в стек, поэтому то, сколько дополнительных данных потребуется для сбоя вашей программы, зависит от того, что компилятор поместил в стек.

Некоторые компиляторы могут выдавать код, который приведет к аварийному завершению работы всего на один байт больше размера буфера - поведение не определено.

Я предполагаю, что размера 13 достаточно, чтобы перезаписать адрес возврата или что-то подобное, что приводит к сбою при возврате вашей функции.Но другой компилятор или другая платформа могут / выйдут из строя с другой длиной.

Кроме того, ваша программа могла бы аварийно завершать работу с другой длиной, если бы она выполнялась в течение более длительного времени, если бы перезаписывалось что-то менее важное.

Для 32-разрядной платформы Intel объяснение заключается в следующем.Когда вы объявляете char[5] в стеке, компилятор действительно выделяет 8 байт из-за выравнивания.Тогда для функций типично иметь следующий ввод:

push ebp
mov ebp, esp

это сохраняет значение реестра ebp в стеке, затем перемещает значение регистра esp в ebp для использования значения esp для доступа к параметрам.Это приводит к тому, что еще 4 байта в стеке будут заняты значением ebp.

В epilogue ebp восстанавливается, но его значение обычно используется только для доступа к параметрам функции, выделенным стеком, поэтому его перезапись в большинстве случаев не повредит.

Итак, у вас есть следующая компоновка (стек растет вниз на Intel):8 байт для вашего массива, затем 4 байта для ebp, затем обычно обратный адрес.

Вот почему вам нужно перезаписать не менее 13 байт, чтобы завершить работу вашей программы.

Чтобы добавить к приведенным выше ответам:вы можете проверить наличие подобных ошибок с помощью такого инструмента, как Валгринд.Если вы работаете в Windows, взгляните на это ТАК нить.

Это зависит от того, что находится в стеке после массива "str".Вы просто случайно не наступаете ни на что критическое, пока не скопируете такое количество символов.

Так что это будет зависеть от того, что еще есть в функции, от используемого вами компилятора и, возможно, от параметров компилятора тоже.

13 равно 5 + 8, предполагая, что после массива str есть два некритичных слова, затем что-то критическое (возможно, обратный адрес)

В этом чистая прелесть неопределенного поведения (UB):это не определено.

Ваш код:

char str[5];
strcpy(str,"Hello12345678");

Записывает 14 байт / символов в str который может содержать только 5 байт / символов.Это вызывает UB.

Q:Итак, почему это не сбой для "Hello1234567", а сбой только для "Hello12345678", т. Е. строки длиной 13 или более 13.

  • Потому что поведение не определено.Используйте strncpy.Смотрите эту страницу http://en.wikipedia.org/wiki/Strcpy для получения дополнительной информации.

Потому что поведение не определено.Используйте strncpy.Смотрите эту страницу http://en.wikipedia.org/wiki/Strcpy для получения дополнительной информации.

strncpy небезопасен, поскольку он не добавляет нулевое завершение, если исходная строка имеет длину >= n, где n - размер целевой строки.

char s[5];
strncpy(s,5,"test12345");
printf("%s",s); // crash

Мы всегда используем strlcpy, чтобы облегчить эту проблему.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top