Почему куча не повредилась раньше?

#c #memory-management #heap-memory

#c #управление памятью #куча-память

Вопрос:

Я пытаюсь понять на более низком уровне, как C управляет памятью. Я нашел некоторый код на веб-странице, цель которого — показать вам, насколько плохим может быть плохое управление памятью, поэтому я скопировал, вставил его и скомпилировал:

 int main(int argc, char **argv) {
        char *p, *q;
        p = malloc(1024);
        q = malloc(1024);
        if (argc >= 2)
                strcpy(p, argv[1]);
        free(q);
        free(p);
        return 0;
}
 

Тестовые примеры были выполнены с помощью общей команды

 /development/heapbug$ ./heapbug `perl -e 'print "A"x$K'`
 

Ибо $K < 1023 я не ожидал проблем, но $K = 1024 я ожидал дампа ядра, которого не произошло. Короче говоря, у меня начались проблемы с сегментами $K > 1033 .

Два вопроса: 1) почему это произошло? 2) существует ли формула, которая определяет «толерантность» системы?

Комментарии:

1. UB не соответствует «формулам». Отсюда и слово «неопределенный».

2. Нет «как C управляет памятью». Существует только то, как конкретные реализации C управляют памятью, и это варьируется. Лично я не склонен придавать большое значение изучению особенностей конкретных реализаций.

3. @JohnBollinger: Я имел в виду — будет ли это побочным эффектом подкачки памяти?

Ответ №1:

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

Кроме того, внесение, казалось бы, несвязанных изменений, таких как добавление неиспользуемой локальной переменной или printf вызов отладки, может изменить то, как проявляется неопределенное поведение, а также компиляция с другим компилятором или с тем же компилятором с разными настройками оптимизации.

То, что программа может аварийно завершиться, не означает, что это произойдет.

Тем не менее, то, что, вероятно, произошло, связано с тем, как malloc это реализовано в вашей системе. Вероятно, он откладывает на несколько байт больше, чем было запрошено для целей выравнивания и ведения бухгалтерского учета. Без агрессивной оптимизации эти дополнительные байты для выравнивания, вероятно, не используются ни для чего другого, поэтому вы получаете awya при записи в них, но тогда у вас возникают проблемы, когда вы записываете дальше в байты, чем может содержать внутренние структуры, используемые malloc и free поврежденные вами.

Но опять же, вы не можете зависеть от такого поведения. C зависит от того, будет ли разработчик следовать правилам, и если вы этого не сделаете, случится что-то плохое.

Ответ №2:

Неопределенное поведение — это как раз то, что нужно. Он может разбиться. А может, и нет. Это может работать безупречно. Это может выпить все молоко в вашем холодильнике. Он может украсть вашу любимую пару туфель и топтать их по грязи.

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

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

Ответ №3:

Насколько я понимаю, если вы переполняете память, контролируемую в пользовательском пространстве вашего приложения (code / stack / etc), это не гарантирует, что вызовет основной дамп, и действительно может перезаписать часть этой памяти, что является риском, связанным с непреднамеренным переполнением буфера.

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

Ответ №4:

Запись в нераспределенную память — это неопределенное поведение. Результат не уточняется. Это может привести к сбою, а может и не привести. Переполнение кучи может привести к повреждению содержимого других адресов памяти, но как это повлияет на программу, неизвестно.