Как определение переменной const char* косвенно вызывает сброс ядра?

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