Ошибка сегментации в strcpy
-
23-08-2019 - |
Вопрос
рассмотрим приведенную ниже программу
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, чтобы облегчить эту проблему.