2009-03-28 2 views
5

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

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

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

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

ответ

3

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

ОБНОВЛЕНИЕ: Threading в вашем приложении сделает это очень сложным. См. Другие ответы на вопросы о потоке.

+0

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

+0

Но почему? Операционная система уже поддерживает список выделенной памяти. Вы дублируете эту функциональность, просто замедляете процесс закрытия без каких-либо преимуществ, кроме как немного инфляции эго. Если вы не планируете переносить свое приложение на MSDOS, пусть os выполнит свою работу. – Eclipse

+0

Причина в том, что в конечном итоге дисциплина и функциональность помогут вам отслеживать утечки памяти. Если вы правильно закодируете его, его можно отключить с помощью определений DEFINE или MACRO. – ojblass

3

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

+0

Это не всегда так. Считается хорошей санитарной практикой освобождать структуры malloc'ed перед выходом. Это также помогает, когда вам нужно найти фактические утечки памяти. –

+0

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

+0

Правильно, я предполагаю, что он обсуждает критические неудачи; то есть процесс умирает и быстро умирает. – Michael

1

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

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

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

3

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

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

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

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

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

0

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

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

1

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

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

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

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

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

Большинство оставшихся «узоров» - это несколько ad hoc.

0

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

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

В зависимости от системы, конечно, можете ли вы или должны позволить использовать его.

1

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

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 from the MPS (который по совпадению является системой управления памятью).

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

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

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

+0

Это плохо, потому что вы используете goto. Извините, не удалось устоять: D –

+0

Если вы инициализировали свои 'baz' и' bar' с помощью 'NULL' и сделали свою функцию уничтожения так, чтобы они обрабатывали параметр' NULL' изящно (например, 'free'), тогда вы бы иметь возможность использовать только одну метку «fail:», делая ее немного менее переполненной. –

+0

Спасибо за предложение! Это правило кодирования в проектах с высокой степенью надежности, чтобы избежать использования NULL практически для чего угодно. Наличие NULL вокруг приводит ко всем видам ошибок. Мы особенно избегаем использования NULL для указания специального действия, которое вы должны предпринять. Но есть еще одно эссе, которое я мог бы написать о NULL: P – rptb1

Смежные вопросы