Утечки памяти ... Объяснил (надеюсь) - PullRequest
4 голосов
/ 02 февраля 2011

Может ли кто-нибудь помочь мне понять концепцию утечки памяти и как конкретные структуры данных способствуют / предотвращают ее (например, связанные списки, массивы и т. Д.). Меня уже дважды учили этому два разных человека, что немного смутило меня из-за различий в методах обучения.

Ответы [ 5 ]

7 голосов
/ 02 февраля 2011

В Википедии есть хорошее описание утечек памяти .Здесь дано следующее определение:

A memory leak, in computer science (or leakage, in this context), occurs 
when a computer program consumes memory but is unable to release it back 
to the operating system.

Например, следующая функция C теряет память:

void leaky(int n)
{
    char* a = malloc(n);
    char* b = malloc(n);
    // Do something with a
    // Do something with b
    free(a);
}

Вышеуказанная функция пропускает n байтов памяти, так как программист забыл вызватьfree(b).Это означает, что операционная система имеет n байтов меньше памяти для удовлетворения дальнейших вызовов malloc.Если программа вызывает leaky много раз, ОС может в конечном итоге исчерпать память, которую она может выделить для других задач.

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

// I guess you could figure out where memory is leaking and fix it.

void delete_element(ListNode* node, int key)
{
    if (node != NULL)
    {
        if (node->key == key)
        {
            if (node->prev != NULL) {
                // Unlink the node from the list.
                node->prev->next = node->next;
            }
        }
        else
        {
            delete_element(node->next, key);
        }  
    }
}
3 голосов
/ 02 февраля 2011

Я согласен с ответом Виджая по большей части, но важно отметить, что утечки происходят, когда ссылки на блоки кучи (указатели) теряются.Существуют две основные причины:

1 - потеря области указателя

void foo(void)
{
    char *s;
    s = strdup("U N I C O R N S ! ! !");
    return;
}

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

Однако, если бы мы просто изменили функцию на return strdup("U N I C O R N S ! ! !");, у нас все равно была бы ссылкана блок, выделенный strdup ().

2 - Переопределение указателей без сохранения оригинала

 void foo(void)
 {
      unsigned int i;
      char *s;

      for (i=0; i<100; i++)
         s = strdup("U N I C O R N S ! ! !");

      free(s);
  }

В этом примере мы потеряли 99 ссылокна блоки, на которые s когда-то указывал, так что мы на самом деле освобождаем только один блок в конце.Опять же, эта память теперь теряется до тех пор, пока ОС не восстановит ее после выхода из программы.

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

Следует также отметить, что работа с типом хранения static немного отличается, как обсуждалось в этот ответ .

1 голос
/ 02 февраля 2011

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

  • В языках с ручным управлением памятью, таких как C, это происходит, когда программане удается явно освободить кучу памяти, т. е. программист всегда должен что-то делать, чтобы избежать утечек памяти.
  • В базовых языках сборки мусора, таких как Java, это происходит, когда программа случайно хранит ссылки на объекты, которые больше не нужны,Очень часто это происходит, когда такие объекты добавляются в «глобальную» коллекцию, а затем «забываются» (особенно, когда добавление происходит неявно).

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

Прототип утечки памяти - это кэш (т. Е. Коллекция, которая поддерживается неявно), хранящаяся в статической переменной (т. Е. Максимально долгоживущая).

0 голосов
/ 02 февраля 2011

Я не могу добавить к тому, что говорили другие в отношении определения утечек памяти, но я могу дать вам несколько замечаний о том, когда могут произойти утечки памяти.

Первый случай, который приходит на ум - это функция, которая выполняет распределение:

int* somefunction(size_t sz)
{
    int* mem;
    mem = malloc(sz*sizeof(int));
    return mem;
}

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

int* x = somefunction(5);

И это легко забыть, теперь это не malloc, чтобы освободить x. Опять же, в этом нет ничего, что означало бы, что вы забудете , но мой опыт подсказывает мне, что я и другие это упускаем из виду.

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

Второй случай, который приходит на ум, - это потоки, особенно fork(), потому что весь код как бы в одном месте. Если вы аккуратно кодируете с помощью функций, нескольких файлов и т. Д., Вы почти всегда избегаете ошибок, но помните, что все должно быть свободно в некоторой области, включая то, что было выделено как post fork (), так и pre fork. Учтите это:

int main()
{
    char* buffer = malloc(100*sizeof(char));
    int fork_result = fork();

    if ( fork_result < 0 )
    {
        printf("Error\n");
        return 1;
    }
    elseif ( fork_result == 0 )
    {
        /* do child stuff */
        return 0;
    }
    else
    {
        /* do parent stuff */
    }

    free(buffer);
    return 0;
}

Здесь есть небольшая ошибка. Родитель не будет пропускать память, но child делает , потому что это точная копия родительского объекта, включая кучу, но она выходит перед освобождением чего-либо. Свободное должно происходить на обоих путях кода. Точно так же, если вилка не удалась, вы все равно не освободились. Легко пропустить вещи, когда вы пишете такой код. Лучший способ - создать переменную кода выхода, такую ​​как int status = 0;, и изменить ее при возникновении ошибок, а не использовать return в любой структуре , но разрешить пути дочернего и родительского кода продолжать до конца запрограммируйте как они должны.

Тем не менее, многопоточность и разветвление всегда затрудняют отладку из-за их природы.

0 голосов
/ 02 февраля 2011

Ответ, предоставленный Виджаем , показывает, как вызывает утечку памяти. Но обнаружение утечки может быть довольно сложной задачей, когда ваша программа выходит за рамки нескольких строк кода.

Если вы работаете в Linux, valgrind может помочь вам найти утечки.

В Windows вы можете использовать CRT Debug Heap , которая отображает то, что просочилось, но не то, где оно было выделено. Чтобы отобразить , где выделена утечка памяти, вы можете использовать Memory Validator , который довольно безболезнен в использовании: либо запустите вашу программу под капотом Memory Validator, либо подключитесь к запущенному процессу , Никаких изменений в источниках не требуется. Они обеспечивают 30-дневную пробную версию, которая полностью функциональна.

...