Понимание распределения памяти, сбой тестовой программы

Я почти закончил читать K&R, и это все, что я знаю о Си. Вся моя компиляция выполняется из командной строки Windows с использованием MinGW, и я не знаю продвинутых методов отладки (отсюда комментарий «отладка гетто» в моей 2-й программе ниже).

Я пытаюсь сделать несколько небольших тестовых программ, чтобы помочь мне лучше понять, как работает распределение памяти. Эти первые две программы не используют malloc или free, я просто хотел посмотреть, как память выделяется и освобождается для стандартных массивов, локальных для функции. Идея состоит в том, что я наблюдаю за использованием ОЗУ запущенными процессами, чтобы увидеть, соответствует ли оно тому, что я понимаю. Для этой первой программы ниже она работает так, как я ожидал. Функция alloc_one_meg() выделяет и инициализирует 250 000 4-байтовых целых чисел, но этот МБ освобождается, как только функция возвращается. Поэтому, если я вызову эту функцию 1000000 раз подряд, я никогда не увижу, что использование моей оперативной памяти превышает 1 МБ. И это работает.

#include <stdio.h>
#include <stdlib.h>

void alloc_one_meg() {
    int megabyte[250000];
    int i;
    for (i=0; i<250000; i++) {
        megabyte[i] = rand();
    }
}

main()
{
    int i;
    for (i=0; i<1000000; i++) {
        alloc_one_meg();
    }
}

Для этой второй программы, представленной ниже, идея заключалась в том, чтобы не допустить выхода функции, чтобы одновременно выполнялось 1000 копий одной и той же функции, чего я добился с помощью рекурсии. Моя теория заключалась в том, что программа будет потреблять 1 ГБ ОЗУ, прежде чем она освободит все это после завершения рекурсии. Однако он не проходит второй цикл рекурсии (см. мой комментарий по отладке гетто). Программа вылетает с довольно неинформативным (для меня) сообщением (всплывающее окно Windows о том, что ____.exe столкнулся с проблемой). Обычно я всегда могу добраться до сути вещей с помощью своего метода отладки из гетто... но здесь он не работает. Я в тупике. В чем проблема с этим кодом? Спасибо!

#include <stdio.h>
#include <stdlib.h>

int j=0;

void alloc_one_meg() {
    int megabyte[250000];
    int i;
    for (i=0; i<250000; i++) {
        megabyte[i] = rand();
    }
    j++;
    printf("Loop %d\n", j); // ghetto debug
    if (j<1000) {
        alloc_one_meg();
    }
}

main()
{
    alloc_one_meg();
}

Последующий вопрос опубликован здесь.


c
person The111    schedule 23.12.2011    source источник
comment
И теперь, когда у вас произошел сбой, связанный с переполнением стека, вы стали лучше понимать память. Забавно, как это работает.   -  person Thomas Eding    schedule 23.12.2011
comment
Да, я новичок в программировании, но не новичок в идее тестирования. Я инженер и большой пользователь программного обеспечения, и я очень хорошо знаю, что ломать вещи - лучший способ понять их! :-)   -  person The111    schedule 23.12.2011


Ответы (4)


Вы сталкиваетесь с переполнением стека.

Локальные переменные автоматического хранения (такие как megabyte) размещаются в стеке, который имеет ограниченный объем пространства. malloc размещает память в куче, что позволяет выделять гораздо больше памяти.

Вы можете прочитать больше здесь:

http://en.wikipedia.org/wiki/Stack_overflow

(Я должен отметить, что язык C не указывает, где выделяется память - стек и куча являются деталями реализации)

person Pubby    schedule 23.12.2011
comment
Как здорово, я узнал о переполнении стека на stackoverflow.com. Я должен опубликовать это на мете! Спасибо за объяснение и ссылку. - person The111; 23.12.2011
comment
malloc() не имеет большего объема пространства - см. вывод из дампа и количество, зарезервированное для кучи. - person Heath Hunnicutt; 23.12.2011

Размер стека в программе Windows обычно составляет около 1 МБ, поэтому при второй рекурсии вы переполняете стек. Вы не должны выделять такие большие массивы в стеке, используйте malloc и free для выделения и освобождения памяти в куче (нет способа обойти malloc для таких размеров массивов):

void alloc_one_meg() {
    int *megabyte = malloc(sizeof(int) * 250000); // allocate space for 250000
                                                  // ints on the heap
    int i;
    for (i=0; i<250000; i++) {
        megabyte[i] = rand();
    }
    j++;
    printf("Loop %d\n", j); // ghetto debug
    if (j<1000) {
        alloc_one_meg();
    }

    free(megabyte); // DO NOT FORGET THIS
}

Тем не менее, вы действительно можете изменить размер стека программы и сделать его больше (хотя я бы сделал это только в качестве учебного упражнения, а не в рабочем коде). Для Visual Studio вы можете использовать параметр компилятора /F, а в Linux вы можете использовать setrlimit(3). Я не уверен, что использовать с MinGW.

person Seth Carnegie    schedule 23.12.2011
comment
Он сказал, что не хочет использовать malloc/free. - person Pubby; 23.12.2011
comment
Часть большей цели этой серии тестов состояла в том, чтобы помочь мне понять назначение malloc и free. Я хотел сначала запустить несколько тестов без них, а затем несколько тестов с ними. Я знал, что жизнь локальных переменных стека связана с входом/выходом функции, а жизнь переменных кучи связана с malloc/free, но я не знала, что стек имеет такой маленький предел. Так что я уже многому научился в этом тесте. :-) - person The111; 23.12.2011
comment
@Джонсон, да, этому стоит научиться. Стек очень мал по сравнению с кучей, поэтому вы не можете выполнить рекурсию слишком далеко (даже с небольшим количеством локальных переменных), прежде чем произойдет переполнение стека. - person Seth Carnegie; 23.12.2011
comment
@Seth Carnegie - malloc () и free () столь же ограничены - посмотрите вывод dumpbin в двоичном файле, скомпилированном со значениями по умолчанию. Он тоже не может их использовать. Чего он не может не использовать, так это VirtualAlloc(). - person Heath Hunnicutt; 23.12.2011
comment
@HeathHunnicutt Я думаю, что куча больше, чем стек - person Seth Carnegie; 23.12.2011
comment
@HeathHunnicutt, я создал аналогичный тест, используя malloc и free, и, конечно же, я могу без проблем получить использование памяти программы (по сообщениям Windows) до 1 ГБ, если мне не удастся вызвать free. Таким образом, куча кажется как минимум в 1000 раз больше, чем стек. Или, может быть, моя (преднамеренная, академическая) утечка памяти создает иллюзию большой кучи? Или это одно и то же? - person The111; 23.12.2011
comment
@Johnson, это то же самое, см. эту тему (не обращайте внимания на глупые споры о единственном истинном значении фразы «моральный эквивалент»), который указывает на то, что между malloc и VirtualAlloc практически нет разницы. - person Seth Carnegie; 23.12.2011
comment
Спасибо, Сет. Последующий вопрос опубликован: 8611433/ - person The111; 23.12.2011

Память, которую вы выделяете с помощью рекурсивных функциональных вызовов, выделяется из стека. Вся память стека должна быть непрерывной. Когда ваш процесс запускает поток, Windows резервирует диапазон адресного пространства виртуальной памяти для стека этого потока. Объем резервируемой памяти указан в «PE-заголовке» вашего EXE-файла. PE означает «Переносимый исполняемый файл».

Используя утилиту dumpbin, входящую в состав Visual Studio, с самим собой (dumpbin.exe) в качестве входного файла:

dumpbin /headers dumpbin.exe

... есть некоторый вывод, а затем:

      100000 size of stack reserve
        2000 size of stack commit

"100000" – это шестнадцатеричное число, равное 1 048 576, то есть примерно 1 МБ.

Другими словами, операционная система зарезервирует для стека только диапазон адресов размером 1 МБ. Когда этот диапазон адресов израсходован, Windows может или не может выделить дополнительные последовательные диапазоны памяти для увеличения стека. Результат зависит от того, доступен ли дальнейший непрерывный диапазон адресов. Маловероятно, что он будет доступен из-за других выделений, сделанных Windows при запуске потока.

Чтобы выделить максимальный объем виртуальной памяти под Windows, используйте семейство функций VirtualAlloc.

person Heath Hunnicutt    schedule 23.12.2011

Переполнение стека. Это вопрос с подвохом?

person jdigital    schedule 23.12.2011
comment
Нет, я просто нуб. Я очень быстро узнаю, что есть много того, что мне нужно знать о C, чего я не могу получить из K&R (как я уже сказал, пока что это мой единственный источник знаний). Где хорошее место, чтобы узнать о знаниях, которые помешали бы мне задать такой вопрос? Стек/куча и тому подобное (этих понятий нет в K&R, и я понимаю, почему). - person The111; 23.12.2011