Приручение malloc/free зверя — подсказки и приемы

Я использовал C на некоторых проектах для степени магистра, но никогда не создавал производственное программное обеспечение с ним. (.NET и JavaScript являются моим хлебом с маслом.), Очевидно, потребность к free() память, что Вы malloc() очень важно в C. Это прекрасно, хорошо и хорошо, если можно сделать обоих в одной стандартной программе. Но поскольку программы растут, и структуры углубляются, отслеживание того, что было malloc'd то, где и что соответствует свободному, становится более твердым и более твердым.

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

Так: как Вы рекомендуете структурировать свои программы C, чтобы помешать динамическим выделениям становиться утечками памяти?

11
задан roufamatic 18 July 2013 в 18:02
поделиться

5 ответов

Проектирование по контракту. Удостоверьтесь, что каждый комментарий функции явно описывает гигиену ее памяти - то есть, блокируется ли она и чья ответственность заключается в освобождении того, что было выделено, и принимает ли она права собственности на что-либо переданное. И БУДЬТЕ СООТВЕТСТВУЮТ своим функциям.

Например, ваш заголовочный файл может содержать что-то вроде:

/* Sets up a new FooBar context with the given frobnication level.
 * The new context will be allocated and stored in *rv; 
 * call destroy_foobar to clean it up. 
 * Returns 0 for success, or a negative errno value if something went wrong. */
int create_foobar(struct foobar** rv, int frobnication_level);

/* Tidies up and tears down a FooBar context. ctx will be zeroed and freed. */
void destroy_foobar(struct foobar* ctx);

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

8
ответ дан 3 December 2019 в 05:11
поделиться

В крупных проектах часто используется метод «пула»: в этом случае каждое выделение связано с пулом и автоматически освобождается, когда пул освобождается. Это действительно удобно, если вы можете выполнить сложную обработку с помощью одного временного пула, который затем можно будет освободить одним махом, когда вы закончите. Подпулы обычно возможны; часто можно встретить такой шаблон:

void process_all_items(void *items, int num_items, pool *p)
{
    pool *sp = allocate_subpool(p);
    int i;

    for (i = 0; i < num_items; i++)
    {
        // perform lots of work using sp

        clear_pool(sp);  /* Clear the subpool for each iteration */
    }
}

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

Недостатки:

  • Выделенное время жизни объекта может быть немного больше, так как вам придется ждать, пока пул будет очищен или освобожден.
  • В конечном итоге вы передаете функциям дополнительный аргумент пула (где-нибудь, чтобы они могли выделить любое необходимое им пространство).
5
ответ дан 3 December 2019 в 05:11
поделиться

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

4
ответ дан 3 December 2019 в 05:11
поделиться

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

Существуют также более высокоуровневые способы управления памятью в C, например, использование пулов памяти (см., Например, Apache APR ).

3
ответ дан 3 December 2019 в 05:11
поделиться

Абстрагируйте аллокаторы и деаллокаторы для каждого типа. Учитывая определение типа

typedef struct foo
{
  int x;
  double y;
  char *z;
} Foo;

создайте функцию аллокатора

Foo *createFoo(int x, double y, char *z)
{
  Foo *newFoo = NULL;
  char *zcpy = copyStr(z);

  if (zcpy)
  {
    newFoo = malloc(sizeof *newFoo);
    if (newFoo)
    {
      newFoo->x = x;
      newFoo->y = y;
      newFoo->z = zcpy;
    }
  }
  return newFoo;
}

функцию копирования

Foo *copyFoo(Foo f)
{
  Foo *newFoo = createFoo(f.x, f.y, f.z);
  return newFoo;
}

и функцию деаллокатора

void destroyFoo(Foo **f)
{
  deleteStr(&((*f)->z));
  free(*f);
  *f = NULL;
}

Обратите внимание, что createFoo() в свою очередь вызывает функцию copyStr(), которая отвечает за выделение памяти и копирование содержимого строки. Заметим также, что если copyStr() потерпит неудачу и вернет NULL, то newFoo не будет пытаться выделить память и вернет NULL. Аналогично, destroyFoo() вызовет функцию для удаления памяти для z перед тем, как освободить остальную часть структуры. Наконец, destroyFoo() устанавливает значение f в NULL.

Ключевым моментом здесь является то, что аллокатор и деаллокатор делегируют ответственность другим функциям, если элементы-члены также требуют управления памятью. Поэтому, по мере усложнения типов, вы можете повторно использовать эти аллокаторы следующим образом:

typedef struct bar
{
  Foo *f;
  Bletch *b;
} Bar;

Bar *createBar(Foo f, Bletch b)
{
  Bar *newBar = NULL;
  Foo *fcpy = copyFoo(f);
  Bletch *bcpy = copyBar(b);

  if (fcpy && bcpy)
  {
    newBar = malloc(sizeof *newBar);
    if (newBar)
    {
      newBar->f = fcpy;
      newBar->b = bcpy;
    }
  }
  else
  {
    free(fcpy);
    free(bcpy);
  }

  return newBar;
}

Bar *copyBar(Bar b)
{
  Bar *newBar = createBar(b.f, b.b);
  return newBar;
}

void destroyBar(Bar **b)
{
  destroyFoo(&((*b)->f));
  destroyBletch(&((*b)->b));
  free(*b);
  *b = NULL;
}

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

Это позволит вам выделять и деаллоцировать память для объектов в последовательном, четко определенном порядке, что составляет 80% успеха в управлении памятью. Остальные 20% - это обеспечение того, чтобы каждый вызов аллокатора уравновешивался деаллокатором, что является действительно трудной частью.

edit

Изменены вызовы функций delete*, чтобы я передавал правильные типы.

2
ответ дан 3 December 2019 в 05:11
поделиться
Другие вопросы по тегам:

Похожие вопросы: