#c
Вопрос:
Я запустил это:
int main(){
//const char* variable="Hello, World!";//random string
for(char i=0;i<10;i ){//random limit
char* arr;
arr[0]=42;//random number
}
return 0;
}
Он не сбросил ядро. Но когда я раскомментировал прокомментированную строку и запустил ее снова, она выдала это сообщение об ошибке:
/usr/bin/timeout: the monitored command dumped core
sh: line 1: 14403 Segmentation fault /usr/bin/timeout 10s main
Я использовал https://www.tutorialspoint.com/compile_c_online.php.
Почему это происходит и что я могу сделать, чтобы предотвратить это?
Комментарии:
1. Строковый литерал не имеет абсолютно никакого отношения к тому, что не так с этим кодом. Код в теле for-lop изобилует неопределенным поведением . Вы разыменовываете неопределенный указатель .
2.
char* arr; arr[0];
. Как вы думаете, что должно произойти?3. Определение не вызывает никакого дампа ядра. Доступ к недопустимой памяти делает это.
4. Вы забыли выделить память
arr
перед попыткой записи в нее. Это пример неопределенного поведения. Любое изменение в программе может привести к изменению поведения. Вот почему в вашем коде не может быть подобных ошибок. Все, что делает другое объявление, — это изменяет расположение вашей памяти. Это не имеет никакого отношения к ошибке, но может повлиять на то, как ошибка проявляется.5. Неопределенное поведение вызывает дамп ядра, а неопределенное поведение вызывает отсутствие дампа ядра. Чтобы предотвратить это, не пишите в своем коде неопределенное поведение.
Ответ №1:
arr[0]=42;
это то же самое, что
*(arr 0)=42;
а также
*arr=42;
Таким образом, вы вводите значение 42 в объект, на который arr
указывает. Тем не менее, вы делаете:
char* arr;
так arr
неинициализирован, и он может указывать «куда угодно», включая незаконные адреса, которые приведут к сбою. Также может случиться так, что он указывает на какой-то юридический адрес, и в этом случае программа, по-видимому, будет работать. Поэтому иногда он выходит из строя, а иногда, кажется, работает. Это вообще называется «неопределенным поведением». Вы не можете знать, что будет делать такой код…
Чтобы предотвратить эту ситуацию, вам необходимо выполнить инициализацию arr
, чтобы указать на допустимый объект.
Например:
char* arr = malloc(sizeof *arr);
Комментарии:
1. Это действительно проблема с их кодом, но я думаю, что они имели в виду, почему раскомментированная строка приведет к ошибке
2. @DuarteArribas, о котором говорится: «Также может случиться так, что он указывает на какой-то юридический адрес…», но я добавил несколько дополнительных слов, чтобы сделать это более понятным
3. Я понимаю это, но я не знаю, почему то же самое не происходит без существования «переменной». В реальном коде у меня уже есть больше, чем просто переменная «arr».
4. @markoj Вот что такое «неопределенное поведение». Может случиться все, что угодно, и невозможно объяснить, почему произошло что-то конкретное. В вашем случае «дополнительная» переменная привела к сбою, но с таким же успехом могло быть и наоборот. Это невозможно объяснить… Лекарство: Напишите код с четко определенным поведением
5. Спасибо, @4386427.
Ответ №2:
То, на что указывает инициализированный указатель arr
, не определено и недетерминировано. Может произойти все, что угодно, включая как кажущееся ничто, так и сброс ядра. Изменение кода просто изменяет то arr
, на что указывает.
В моем тесте на https://onlinegdb.com/Q1k0Fd5oB в обоих случаях он просто выполнялся до завершения (в обоих случаях arr == 0
). Вот в чем дело с неопределенным поведением. Также стоит отметить, что этот код также тривиально оптимизирован до отказа (https://godbolt.org/z/7dTvrGaEf) в этом случае он не будет сбрасывать ядро.
Ответ №3:
Отличный пример неопределенного поведения.
Если вы повредите хотя бы один байт памяти (как вы делаете здесь, записывая в нераспределенный массив), вам, скорее всего, это сойдет с рук на некоторое время (т. Е., Похоже, ничего не произойдет), пока совершенно несвязанное изменение в вашем коде не заставит ваше приложение вести себя всевозможными забавными способами.
Считайте, что вам повезло: сбой является систематическим, и модификация, которая его вызывает, очень близка к источнику ошибки. В реальной жизни это повреждение может привести к непредсказуемому поведению, сбою вашей программы один раз в час или в день, случайному появлению поврежденных данных и т. Д.
И причина этих неполадок может быть обнаружена в совершенно другой части кода, написанной недели или месяцы назад. Правильно, ошибка может бездействовать в течение нескольких месяцев, пока какое-то совершенно несвязанное изменение кода не превратит ее в убийцу приложений. Представьте, что вы просеиваете несколько месяцев разработки кода, чтобы найти источник проблемы.
C и C являются особенно неумолимыми языками, оставляя программиста ответственным за каждый отдельный байт выделенной памяти. Повреждение памяти чрезвычайно легко, и одного байта, записанного там, где его не должно быть, достаточно, чтобы привести к гибели всего приложения.
Мораль этой истории: небрежное программирование не является вариантом в C/C . Если вы не научитесь тщательно тестировать свой код и не освоите некоторые базовые методы оборонительного и наступательного программирования на ранней стадии, вас (и ваших коллег) ждет мир боли.