Шаблоны для освобождения памяти в C? - PullRequest
5 голосов
/ 28 марта 2009

В настоящее время я работаю над приложением на основе C, но немного застрял в освобождении памяти без антипаттерна. Я любитель управления памятью.

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

Как мне освободить мои структуры, если я выйду () в одной области, но не все мои структуры данных находятся в этой области?

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

Ответы [ 8 ]

3 голосов
/ 28 марта 2009

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

Ответ: это зависит. Есть несколько стратегий, которые вы можете использовать.

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

Эта стратегия меняется, например, если вы разрабатываете встроенную операционную систему, где exit() может не очистить все. Обычно я вижу, что когда отдельные функции возвращаются из-за ошибки, они убирают все, что они сами выделили. Вы не увидите никаких вызовов exit() после вызова, скажем, 10 функций. Каждая функция, в свою очередь, будет указывать ошибку при возврате, и каждая функция будет очищаться после себя. Исходная функция main() (если хотите - она ​​может не вызываться main()) обнаружит ошибку, очистит всю выделенную память и предпримет соответствующие действия.

Когда у вас просто есть прицелы, это не ракетостроение. Трудно сделать это, если у вас есть несколько потоков исполнения и общие структуры данных. Тогда вам может понадобиться сборщик мусора или способ подсчета ссылок и освобождения памяти, когда с ним покончит последний пользователь структуры. Например, если вы посмотрите на источник сетевого стека BSD, вы увидите, что он использует значение refcnt (счетчик ссылок) в некоторых структурах, которые необходимо поддерживать «живыми» в течение длительного периода времени и совместно использовать их. среди разных пользователей. (Это в основном то, что делают сборщики мусора.)

3 голосов
/ 28 марта 2009

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

UPDATE: Потоки в вашем приложении сделают это очень сложным. См. Другие ответы о проблемах с потоками.

3 голосов
/ 28 марта 2009

Вам не нужно беспокоиться об освобождении памяти при вызове exit (). Когда процесс завершается, операционная система освобождает всю связанную память.

1 голос
/ 15 июля 2011

Люди уже отметили, что вам, вероятно, не нужно беспокоиться об освобождении памяти, если вы просто выходите (или прерываете) свой код в случае ошибки. Но на всякий случай, вот шаблон, который я разработал и много использую для создания и разрушения ресурсов в случае ошибки. ПРИМЕЧАНИЕ: здесь я показываю шаблон, а не пишу реальный код!

int foo_create(foo_t *foo_out) {
    int res;
    foo_t foo;
    bar_t bar;
    baz_t baz;
    res = bar_create(&bar);
    if (res != 0)
        goto fail_bar;
    res = baz_create(&baz);
    if (res != 0)
        goto fail_baz;
    foo = malloc(sizeof(foo_s));
    if (foo == NULL)
        goto fail_alloc;
    foo->bar = bar;
    foo->baz = baz;
    etc. etc. you get the idea
    *foo_out = foo;
    return 0; /* meaning OK */

    /* tear down stuff */
fail_alloc:
    baz_destroy(baz);
fail_baz:
    bar_destroy(bar);
fail_bar:
    return res; /* propagate error code */
}

Могу поспорить, я получу несколько комментариев, говорящих "это плохо, потому что вы используете goto". Но это дисциплинированное и структурированное использование goto, которое делает код более понятным, простым и легким в обслуживании, если применяется последовательно. Без этого вы не сможете достичь простого, документированного пути разрыва кода.

Если вы хотите увидеть это в реальном коммерческом коде, посмотрите, скажем, arena.c из MPS (который по совпадению является системой управления памятью).

Это своего рода попытка бедняка ... обработчик финиша, который дает вам что-то вроде деструкторов.

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

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

1 голос
/ 28 марта 2009

Совет Майкла здравый - если вы выходите, вам не нужно беспокоиться об освобождении памяти, так как система все равно восстановит ее.

Единственное исключение - сегменты разделяемой памяти - по крайней мере, в разделе System V Shared Memory. Эти сегменты могут сохраняться дольше, чем программа, которая их создает.

Одна из опций, не упомянутых до сих пор, заключается в использовании схемы выделения памяти на основе арены, построенной на основе стандарта malloc(). Если все приложение использует одну арену, ваш код очистки может освободить эту арену, и все сразу освобождается. (APR - Apache Portable Runtime - предоставляет функцию пулов, которая, на мой взгляд, похожа; «Интерфейсы и реализации C» Дэвида Хэнсона предоставляют систему выделения памяти на основе арены; я написал такую, которую вы могли бы использовать, если хотите.) можно думать об этом как о "сборке мусора для бедных".

Как общая дисциплина памяти, каждый раз, когда вы выделяете память динамически, вы должны понимать, какой код собирается освободить ее и когда она может быть освобождена. Есть несколько стандартных шаблонов. Самое простое - «размещается в этой функции; освобождается до возврата этой функции». Это сохраняет память в значительной степени под контролем (если вы не выполняете слишком много итераций в цикле, который содержит выделение памяти), и ограничивает ее, чтобы она могла быть доступна для текущей функции и функций, которые она вызывает. Очевидно, вы должны быть разумно уверены, что вызываемые вами функции не будут сжимать (кэшировать) указатели на данные и пытаться использовать их позже после того, как вы освободили и повторно использовали память.

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

Большинство оставшихся «шаблонов» несколько ad hoc .

1 голос
/ 28 марта 2009

Вы можете создать простой диспетчер памяти для памяти malloc'd, который будет разделен между областями / функциями.

Зарегистрируйте, когда вы используете malloc, отмените регистрацию, когда вы освободите его. Есть функция, которая освобождает всю зарегистрированную память перед вызовом exit.

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

0 голосов
/ 13 ноября 2009

Это звучит как задание для сборщика мусора Boehm.

http://www.hpl.hp.com/personal/Hans_Boehm/gc/

Конечно, зависит от системы, можете ли вы или должны позволить себе ее использовать.

0 голосов
/ 28 марта 2009

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

Таким образом, когда объект больше не используется (ноль ссылок), вы можете безопасно удалить его или автоматически удалить его в вызове декремента подсчета ссылок.

...