Висячие указатели и двойные бесплатно - PullRequest
3 голосов
/ 02 апреля 2010

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

aStruct имеет ряд полей, включая другие массивы.

aStruct *A = NULL, *B = NULL;
A = (aStruct*) calloc(1, sizeof(sStruct));
B = A;
free_aStruct(A);
...
// Bunch of other code in various places.
...
free_aStruct(B);

Есть ли способ написать free_aStruct(X), чтобы free_aStruct(B) грациозно выходил?

void free_aStruct(aStruct *X) {
    if (X ! = NULL) {
        if (X->a != NULL) { free(X->a); x->a = NULL; }
        free(X); X = NULL;
    }
}

Выполнение вышеупомянутого устанавливает только A = NULL, когда вызывается free_aStruct(A);. B теперь болтается.

Как можно избежать / исправить эту ситуацию? Является ли подсчет ссылок единственным жизнеспособным решением? Или есть другие «защитные» подходы к освобождению памяти, чтобы предотвратить взрыв free_aStruct(B);?

Ответы [ 5 ]

5 голосов
/ 02 апреля 2010

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

B = A;

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

B = getref_aStruct(A);

Следующая важная вещь - отслеживать распределение. Некоторые вещи, которые помогают, являются чистой модульностью, сокрытием информации и СУХИМ - не повторяйте себя Вы напрямую вызываете calloc () для выделения памяти, в то время как вы используете функцию free_aStruct () для ее освобождения. Лучше использовать create_aStruct (), чтобы выделить его. Это сохраняет все централизованно и только в одном месте, вместо того, чтобы распределять память по всей вашей кодовой базе.

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

2 голосов
/ 02 апреля 2010

Я не думаю, что вы можете сделать это автоматически, поскольку C возлагает на вас бремя и бремя управления памятью, и, следовательно, вы несете ответственность за то, чтобы за ссылками и, конечно, за висящими указателями следили!

void free_aStruct(aStruct *X){
  if (X ! = NULL){
      if (X->a != NULL){free(X->a); x->a = NULL;}
      free(X); X = NULL;
}
}

Кстати, в приведенной выше проверке if есть опечатка ... использование строчной буквы 'x' вместо 'X' ...

Мои мысли, когда я смотрел на приведенный выше кодявляется то, что вы делаете бесплатно на копию переменной указателя типа aStruct *.Вместо этого я бы изменил его на вызов по ссылке ...

void free_aStruct(aStruct **X){
  if (*X ! = NULL){
      if (*X->a != NULL){
          free(*X->a); 
          *X->a = NULL;
      }
      free(*X); 
      *X = NULL;
  }
}

И назвал бы это так:

free_aStruct(&A);

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

1 голос
/ 02 апреля 2010

Подсчет ссылок не так уж и сложен:

aStruct *astruct_getref(aStruct *m)
{
    m->refs++;
    return m;
}

aStruct *astruct_new(void)
{
    sStruct *new = calloc(1, sizeof *new);
    return astruct_getref(new);
}

void astruct_free(aStruct *m)
{
    if (--m->refs == 0)
        free(m);
}

(В многопоточной среде вам также может понадобиться добавить блокировку).

Тогда ваш код будет:

aStruct *A = NULL, *B = NULL;
A = astruct_new();
B = astruct_getref(A);
astruct_free(A);
...
//bunch of other code in various places.
...
astruct_free(B);

Вы спрашивали о блокировке. К сожалению, нет единого универсального ответа, когда дело доходит до блокировки - все зависит от того, какие шаблоны доступа есть в вашем приложении. Там нет замены для тщательного проектирования и глубоких мыслей. (Например, если вы можете гарантировать , что ни один поток не будет вызывать astruct_getref() или astruct_free() для aStruct другого потока, тогда счетчик ссылок вообще не нужно защищать - простой реализации выше будет достаточно).

Тем не менее, вышеуказанные примитивы могут быть легко расширены для поддержки одновременного доступа к функциям astruct_getref() и astruct_free():

aStruct *astruct_getref(aStruct *m)
{
    mutex_lock(m->reflock);
    m->refs++;
    mutex_unlock(m->reflock);
    return m;
}

aStruct *astruct_new(void)
{
    sStruct *new = calloc(1, sizeof *new);
    mutex_init(new->reflock);
    return astruct_getref(new);
}

void astruct_free(aStruct *m)
{
    int refs;

    mutex_lock(m->reflock);
    refs = --m->refs;
    mutex_unlock(m->reflock);
    if (refs == 0)
        free(m);
}

... но обратите внимание, что любые переменные, содержащие указатели на структуры, которые подлежат одновременному доступу, также потребуют своей собственной блокировки (например, если у вас есть глобальный aStruct *foo, к которому одновременно осуществляется доступ, ему потребуется сопровождающий foo_lock).

1 голос
/ 02 апреля 2010

Есть методы, которые вы можете использовать, но суть в том, что ничто из того, что вы делаете, не может быть строго реализовано в C. Вместо этого я рекомендую включить valgrind (или очистить) в процесс разработки. Кроме того, некоторые статические анализаторы кода могут обнаруживать некоторые из этих проблем.

1 голос
/ 02 апреля 2010

Даже если бы вы могли предотвратить взрыв free_aStruct (B), если в коде позади вашего комментария есть какая-либо ссылка на B, он будет использовать освобожденную память и поэтому может быть перезаписан новыми данными в любой момент. точка. Простое «исправление» бесплатного звонка маскирует только основную ошибку.

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