Подтвердить что ты не робот

Почему программа, обращающаяся к нелегальному указателю на указатель, не падает?

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

Я передал эту программу в Windows, Linux, OpenVMS и Mac OS, и они никогда не жаловались.

#include <stdio.h>
#include <string.h>

void printx(void *rec) { // I know this should have been a **
    char str[1000];
    memcpy(str, rec, 1000);
    printf("%*.s\n", 1000, str);
    printf("Whoa..!! I have not crashed yet :-P");
}

int main(int argc, char **argv) {
    void *x = 0; // you could also say void *x = (void *)10;
    printx(&x);
}
4b9b3361

Ответ 1

Я не удивлен недостатком памяти. Программа не разыменовывает неинициализированный указатель. Вместо этого он копирует и печатает содержимое памяти, начиная с переменной указателя, и 996 (или 992) байта за ней.

Поскольку указатель является переменной стека, он печатает память рядом с верхней частью стека для путей вниз. Эта память содержит фрейм стека main(): возможно, некоторые сохраненные значения регистра, количество аргументов программы, указатель на аргументы программы, указатель на список переменных среды и сохраненный регистр команд для main() для возврата, обычно в стартовом коде библиотеки времени выполнения C. Во всех реализациях, которые я исследовал, ниже представлены стековые кадры, которые имеют копии самих переменных среды, массив указателей на них и массив указателей на аргументы программы. В средах Unix (которые вы намекаете, что используете) строки аргументов программы будут ниже.

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

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

Этот код не будет настолько безвредным, если какое-либо из условий выполнения C не соответствует действительности:

  • В архитектуре используется стек
  • Локальная переменная (void *x) выделяется в стеке
  • Стек растет в сторону более низкой нумерованной памяти
  • Параметры передаются в стек
  • Вызывается main() с аргументами. (Некоторые среды с низкой нагрузкой, такие как встроенные процессоры, вызывают main() без параметров.)

Во всех основных современных реализациях все они в целом верны.

Ответ 2

Недопустимый доступ к памяти - это undefined поведение. Это означает, что ваша программа может потерпеть крах, но не гарантируется, потому что точное поведение undefined.

(Шутка среди разработчиков, особенно когда речь идет о коллегах, которые небрежно относятся к таким вещам, заключается в том, что "вызов undefined поведения может форматировать ваш жесткий диск, это просто не гарантируется".;-))

Обновление: Здесь происходит горячее обсуждение. Да, разработчики системы должны знать, что на самом деле происходит в данной системе. Но такие знания привязаны к процессору, операционной системе, компилятору и т.д. И, как правило, имеют ограниченную полезность, потому что даже если вы создадите код, он все равно будет очень низкого качества. Вот почему я ограничил свой ответ на самый важный момент, и фактический вопрос спросил ( "почему это не крушение" ):

Код, размещенный в вопросе, не имеет четко определенного поведения, но это означает, что вы не можете действительно полагаться на то, что он делает, а не на то, что он должен потерпеть крах.

Ответ 3

Если вы разыскиваете неверный указатель, вы вызываете поведение undefined. Это означает, что программа может рухнуть, она может работать, она может приготовить кофе, что угодно.

Ответ 4

Если у вас

int main(int argc, char **argv) {
    void *x = 0; // you could also say void *x = (void *)10;
    printx(&x);
}

Вы объявляете x как указатель со значением 0, и этот указатель живет в стеке, так как он является локальной переменной. Теперь вы переходите к printx адресу x, что означает, что с

memcpy(str, rec, 1000);

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

Ответ 5

Это будет сбой с большой вероятностью, если вы напишете в непривычную область. Но вы читаете, все в порядке. Но поведение будет по-прежнему undefined.