Вопрос о поведении неинициализированных указателей на целые числа при использовании в функции printf - PullRequest
4 голосов
/ 20 июня 2020

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

В книге C Programming: A Modern Approach (автор - KN King) написан следующий отрывок:

Если переменная-указатель p не был инициализирован, попытка использовать значение p каким-либо образом вызывает неопределенное поведение . В следующем примере вызов printf может печатать мусор, заставлять программу cra sh или иметь другой эффект :

int *p;
printf("%d", *p);

Насколько я понимаю указатели и то, как компилятор обрабатывает их, объявление int *p фактически говорит: «Эй, если вы разыменуете p в будущем, я посмотрю на блок из четырех последовательных байтов в памяти, чей начальный адрес является значением, содержащимся в p, и интерпретировать эти 4 байта как целое число со знаком. "

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

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

Комментарии к случаям, пронумерованным выше:

Насколько я понимаю, неопределенное поведение заключается в том, что при запуске время, всякое бывает . С учетом сказанного, в приведенном выше коде мне кажется, что может произойти только очень определенное подмножество вещей. Я понимаю, что p (из-за отсутствия инициализации) хранит случайный адрес, который может указывать где угодно в памяти. Однако, когда printf передается разыменованное значение *p, не будет ли компилятор просто смотреть на 4 последовательных байта памяти (которые начинаются с любого случайного адреса) и интерпретировать эти 4 байта как целое число со знаком?

Следовательно, printf должен делать только одно: выводить число в диапазоне от -2 147 483 648 до 2 147 483 647. Ясно, что это различных возможных выходов, но действительно ли это квалифицируется как «неопределенное поведение». Кроме того, как такое «неопределенное поведение» могло привести к «program cra sh» или «иметь какой-то другой эффект».

Любые разъяснения были бы очень признательны! Спасибо!

Ответы [ 2 ]

4 голосов
/ 20 июня 2020

Значение неинициализированного значения: неопределенное . Он может содержать любое значение (включая 0), и даже возможно, что при каждой попытке чтения может считываться другое значение. Также возможно, что значение могло быть представлением ловушки , что означает, что попытка его чтения вызовет исключение процессора, которое может sh взломать программу.

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

Обратите внимание, что в обоих этих сценариях ios ошибка sh возникает раньше printf даже вызывается.

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

Что касается , почему выполнение этих действий является неопределенным поведением, это потому, что C стандарт говорит так. В частности, приложение J2 дает в качестве примера неопределенного поведения:

Значение объекта с автоматической c продолжительностью хранения используется, пока оно не определено. (6.2.4, 6.7.9, 6.8)

2 голосов
/ 20 июня 2020

Undefined Behavior определяется как «мы не указываем, что должно произойти, это зависит от разработчиков».

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

Примечательно, что если бы p был определен как char *, и вы распечатали его, он бы попробуйте распечатать содержимое, пока не найдете 0x00. Если это приведет вас к границе памяти, вы можете получить ошибку сегментации.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...