Ошибка сегментации в strcpy - PullRequest
       16

Ошибка сегментации в strcpy

6 голосов
/ 05 апреля 2009

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

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

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

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

strcpy(str,"Hello1234567");

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

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

Эта программа была запущена на 32-битном компьютере.

Ответы [ 8 ]

31 голосов
/ 05 апреля 2009

Существует три типа поведения стандартов, которые вас интересуют.

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

Вот что они имеют в виду под неопределенным: вы не не знаете , что произойдет.

7 голосов
/ 05 апреля 2009

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

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

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

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

5 голосов
/ 05 апреля 2009

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

push ebp
mov ebp, esp

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

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

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

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

2 голосов
/ 05 апреля 2009

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

1 голос
/ 05 апреля 2009

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

Ваш код:

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

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

1 голос
/ 05 апреля 2009

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

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

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

0 голосов
/ 06 апреля 2009

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

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

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

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

0 голосов
/ 05 апреля 2009

В: Так почему не происходит сбой для «Hello1234567», а только сбой для «Hello12345678», то есть строки длиной 13 или более 13.

  • Потому что поведение не определено. Используйте strncpy. См. Эту страницу http://en.wikipedia.org/wiki/Strcpy для получения дополнительной информации.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...