it-roy-ru.com

Это хороший способ освободить память?

Функция для освобождения экземпляра struct Foo приведена ниже: 

void DestroyFoo(Foo* foo)
{
    if (foo) free(foo);
}

Вместо этого мой коллега предложил следующее:

void DestroyFoo(Foo** foo)
{
    if (!(*foo)) return;
    Foo *tmpFoo = *foo;
    *foo = NULL; // prevents future concurrency problems
    memset(tmpFoo, 0, sizeof(Foo));  // problems show up immediately if referred to free memory
    free(tmpFoo);
}

Я вижу, что установка указателя на NULL после освобождения лучше, но я не уверен в следующем:

  1. Нам действительно нужно назначить указатель на временный? Помогает ли это с точки зрения параллелизма и общей памяти?

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

55
Ferit Buyukkececi

Нам действительно нужно назначить указатель на временный? Является ли помочь с точки зрения параллелизма и общей памяти?

Это не имеет ничего общего с параллелизмом или общей памятью. Это бессмысленно.

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

Нет, совсем нет.

Решение, предложенное вашим коллегой, ужасно. Вот почему:

  • Установка целого блока в 0 также ничего не дает. Поскольку кто-то случайно использует блок free (), он не узнает об этом, основываясь на значениях в блоке. Это тот тип блока, который возвращает calloc() . Поэтому невозможно узнать, является ли это недавно выделенной памятью (calloc() или malloc()+memset()) или той, которая была освобождена () 'ранее вашим кодом. Во всяком случае, это дополнительная работа для вашей программы, чтобы обнулить каждый блок памяти, который свободен () '.

  • free(NULL); четко определен и не может использоваться, поэтому условие if в if(ptr) {free(ptr);} ничего не дает.

  • Так как free(NULL); не работает, установка указателя на NULL фактически скрыла бы эту ошибку, потому что, если какая-то функция на самом деле вызывает free() для уже свободного () 'ed-указателя, они бы этого не знали.

  • большинство пользовательских функций в начале имеют проверку NULL и могут не рассматривать передачу NULL в качестве условия ошибки: 

void do_some_work(void *ptr) {
    if (!ptr) {
        return; 
    }

   /*Do something with ptr here */
}

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

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

Нет простого способа «случайно» вызвать free() дважды или более. Программист обязан следить за всей выделенной памятью и free() соответствующим образом. Если кому-то трудно с этим справиться, то, вероятно, C не подходит им.

67
P.P.

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

С вашим кодом это, скорее всего, приведет к сбою:

Foo* foo = malloc( sizeof(Foo) );
DestroyFoo(foo);
DestroyFoo(foo); // will call free on memory already freed

С версией кода ваших коллег это не приведет к сбою:

Foo* foo = malloc( sizeof(Foo) );
DestroyFoo(&foo);
DestroyFoo(&foo); // will have no effect

Теперь для этого конкретного сценария достаточно выполнить tmpFoo = 0; (в пределах DestroyFoo). memset(tmpFoo, 0, sizeof(Foo)); предотвратит сбой, если у Foo есть дополнительные атрибуты, к которым можно было бы получить неправильный доступ после освобождения памяти.

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

9
jpo38

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

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

Foo* foo = malloc( sizeof(Foo) );
DestroyFoo(foo);
foo = NULL;

Более того, я не знаю, почему люди проверяют, равен ли NULL указатель перед вызовом free (). Это не нужно, так как free () сделает всю работу за вас.

Установка памяти в 0 (или что-то еще) является хорошей практикой, поскольку free () не очищает память. Он просто помечает область памяти как свободную, чтобы ее можно было использовать повторно. Если вы хотите очистить память, чтобы никто не смог ее прочитать, вам нужно почистить ее вручную. Но это довольно тяжелая операция, и поэтому ее не следует использовать для освобождения всей памяти. В большинстве случаев освобождения без очистки достаточно, и вам не нужно жертвовать производительностью, чтобы выполнить ненужную операцию.

4
codewarrior
void destroyFoo(Foo** foo)
{
    if (!(*foo)) return;
    Foo *tmpFoo = *foo;
    *foo = NULL;
    memset(tmpFoo, 0, sizeof(Foo));
    free(tmpFoo);
}

Ваш коллеги код плохой, потому что

  • произойдет сбой, если foo равен NULL
  • нет смысла создавать дополнительную переменную
  • нет смысла устанавливать значения в нули
  • освобождение структуры напрямую не работает, если она содержит вещи, которые должны быть освобождены

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

Foo* a = NULL;
Foo* b = createFoo();

destroyFoo(NULL);
destroyFoo(&a);
destroyFoo(&b);

В таком случае так должно быть. попробуйте здесь

void destroyFoo(Foo** foo)
{
    if (!foo || !(*foo)) return;
    free(*foo);
    *foo = NULL;
}

Сначала нам нужно взглянуть на Foo, давайте предположим, что это выглядит так

struct Foo
{
    // variables
    int number;
    char character;

    // array of float
    int arrSize;
    float* arr;

    // pointer to another instance
    Foo* myTwin;
};

Теперь, чтобы определить, как он должен быть уничтожен, давайте сначала определим, как он должен быть создан

Foo* createFoo (int arrSize, Foo* twin)
{
    Foo* t = (Foo*) malloc(sizeof(Foo));

    // initialize with default values
    t->number = 1;
    t->character = '1';

    // initialize the array
    t->arrSize = (arrSize>0?arrSize:10);
    t->arr = (float*) malloc(sizeof(float) * t->arrSize);

    // a Foo is a twin with only one other Foo
    t->myTwin = twin;
    if(twin) twin->myTwin = t;

    return t;
}

Теперь мы можем написать функцию уничтожения напротив функции создания

Foo* destroyFoo (Foo* foo)
{
    if (foo)
    {
        // we allocated the array, so we have to free it
        free(t->arr);

        // to avoid broken pointer, we need to nullify the twin pointer
        if(t->myTwin) t->myTwin->myTwin = NULL;
    }

    free(foo);

    return NULL;
}

Test попробуйте здесь

int main ()
{
    Foo* a = createFoo (2, NULL);
    Foo* b = createFoo (4, a);

    a = destroyFoo(a);
    b = destroyFoo(b);

    printf("success");
    return 0;
}
1
Khaled.K

К сожалению, эта идея просто не работает.

Если цель состояла в том, чтобы поймать удвоение, это не распространяется на следующие случаи.

Предположим, этот код:

Foo *ptr_1 = (FOO*) malloc(sizeof(Foo));
Foo *ptr_2 = ptr_1;
free (ptr_1);
free (ptr_2); /* This is a bug */

Предложение должно написать вместо:

Foo *ptr_1 = (FOO*) malloc(sizeof(Foo));
Foo *ptr_2 = ptr_1;
DestroyFoo (&ptr_1);
DestroyFoo (&ptr_2); /* This is still a bug */

Проблема заключается в том, что второй вызов DestroyFoo() по-прежнему завершается сбоем, поскольку ptr_2 не сбрасывается в NULL и по-прежнему указывает на уже освобожденную память.

0
Marc Alff