C крошечный, предварительно выделенный массив не переполняется

Я ожидал segfault с этим кодом:

char * foo (char my_ascii[10])
{
  strcpy (my_ascii, "0123456789");

  return my_ascii;
}

char bar[2];

printf("%s\n", foo (bar));

Потому что bar резервирует массив из 2 символов в стеке, а foo() пытается записать 10 символов. Однако printf() записывает в стандартный вывод 10 символов, и ошибок не возникает. Почему это происходит?

Кроме того, если я изменю функцию foo() следующим образом:

char * foo (char my_ascii[1])
{
  strcpy (my_ascii, "0123456789");

  return my_ascii;
}

Поведение точно такое же: 10 символов копируются в my_ascii. Любое объяснение?

Заранее большое спасибо.

5 ответов

Указание длины параметра массива, например

char * foo (char my_ascii[1]) ...

не имеет значения, так как он опущен (массив распадается на указатель внутри функции).

Кроме того, переполнение буфера является неопределенным поведением, что означает только это: нет никакой гарантии, что программа потерпит крах. Это может совершенно законно выглядеть так, как если бы не было проблем... или генерировать segfault только по четвергам, когда полнолуние... или молча удалять все записи из вашей БД. На самом деле, ничего.

Во-первых, эти определения абсолютно идентичны:

char *foo1(char arr[10]) { /* ... */ }
char *foo2(char arr[1]) { /* ... */ }
char *foo3(char arr[]) { /* ... */ }
char *foo4(char *arr) { /* ... */ }

Во-вторых, запись за пределами объекта - это неопределенное поведение. Все может случиться! Если вам повезет, ваш тестовый запуск рухнет, и вы все исправите; если вам не повезет, ваш тестовый прогон будет работать, так как вы ожидаете, что он потерпит неудачу, только когда вы продемонстрируете его клиенту (или своему боссу).

char * foo (char my_ascii[10]) а также char * foo (char my_ascii[1]) оба эквивалентны char * foo (char *my_ascii)

Примечание. Тип массива превращается в указатель (на первый элемент массива), когда передается функции.

Так как bar резервирует массив из 2 символов в стеке и foo() пытается написать 10 символов. Тем не мение, printf() пишет в стандартный вывод 10 символов и ошибок не возникает. Почему это происходит?

Это потому, что неопределенное поведение означает, что все может случиться.

Только для записи

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

ПРИМЕЧАНИЕ. Возможное неопределенное поведение варьируется от полного игнорирования ситуации с непредсказуемыми результатами до поведения во время перевода или выполнения программы документированным образом, характерным для среды (с выдачей или без выдачи диагностического сообщения), до прекращения перевода или выполнения (с выдача диагностического сообщения).

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

Правда, что bar зарезервировал 2 символа, и вы заполняете его на 8 символов больше, чем он может обработать.

Это не означает автоматически ошибку сегмента.

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

Это хороший пример неопределенного поведения. Неопределенный не означает, что это БУДЕТ потерпеть неудачу, это действительно означает, что поведение не определено; это может сработать, может не получиться, обезьяны могут вылететь из порта USB... все может случиться. В этом случае это на самом деле работает, но вы не можете полагаться на это поведение, потому что оно может измениться при следующем запуске программы.

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


Кстати, есть еще одна ошибка в вашем коде.
Вы описываете my_ascii как 10 символов, но вы пытаетесь скопировать в него 11 символов.
Не забывайте про NULL-терминатор в конце строки!
Это означает, что строка "0123456789" на самом деле требуется 11 символов для хранения.

Другие вопросы по тегам