2009-04-05 1 views
6

рассмотреть программу нижевина Сегментация зЬгсру

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

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

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

strcpy(str,"Hello1234567"); 

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

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

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

+3

Только не делайте этого. – ojblass

+0

@ojblass: Не могу согласиться больше. @ Alien01: Вы отметили этот вопрос C++, так почему бы вам не использовать std :: string? – dalle

+0

Хорошо, я поместил этот трехстрочный код в одну функцию, и я вызываю эту функцию из main. Это единственная функция, которая вызывается из основного и ничего больше в программе. Мое единственное намерение - найти, почему он работает на 12 символов и не работает с 13. – anand

ответ

31

Есть три типа поведения стандартов, вы должны быть заинтересованы.

1/Определено поведение. Это будет работать на всех выполняемых реализациях. Используйте это свободно.

2/Поведение при реализации. Как было сказано, это зависит от реализации, но по крайней мере оно все еще определено. Реализации должны документировать, что они делают в этих случаях. Используйте это, если вы не заботитесь о переносимости.

3/Неопределенное поведение. Все может случиться.И мы имеем в виду что-нибудь, вплоть до вашего компьютера, включая ваш общий компьютер и сворачивающийся, и вы проглатываете себя, вы и большая часть ваших товарищей по работе. Никогда не используйте это. Когда-либо! Шутки в сторону! Не заставляй меня приходить туда.

Копирование более 4 символов и нулевого байта в char[5] - это неопределенное поведение.

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

Увеличьте размер массива до более подходящего (char[14] в этом случае с доступной информацией) или используйте другую структуру данных, которая может справиться.


Update:

Поскольку вы, кажется настолько озабочены выяснить, почему дополнительные 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' 
+------------------------------------+ 

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

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

+0

Говоря о неопределенном поведении и его негативных последствиях, мне нравится эта цитата (хотя я не знаю, к кому ее приписать): «Если вы танцуете босиком по сломанному стеклу неопределенного поведения, вы должны ожидать случайного сокращения «. – SCFrench

+2

Я думаю, что все объяснения неопределенного поведения должны сопровождаться обязательной ссылкой на носовых демонов. –

+0

Очень хороший ответ, действительно! – Makis

7

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

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

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

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

1

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

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

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

+0

Почти наверняка это критически важно, если не к запуску программы, а затем к ее результатам. На самом деле лучше, когда он сбрасывает ядро, так как по крайней мере тогда вы не полагаетесь на, возможно, изворотливые данные. – paxdiablo

+0

Да, я использовал «критический» в узком смысле, который вызывал немедленный крушение. Перезаписать конец массива никогда не будет хорошей идеей. –

1

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

Ваш код:

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

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

2

Чтобы добавить к приведенным выше ответам, вы можете протестировать такие ошибки как с помощью таких инструментов, как Valgrind.Если вы работаете в Windows, посмотрите на this SO thread.

0

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

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

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

push ebp 
mov ebp, esp 

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

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

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

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

0

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

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

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

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

+0

Любая причина для голосования? – Gayan