Когда свободный указатель на C и как узнать, освобожден ли он

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

#include <stdio.h>
#include <stdlib.h>

typedef struct
{
    int a;
} struct1_t;

int main()
{
    funct1(); //init pointer
    return 1;
}


int funct2(struct1_t *ptr2struct)
{
    printf("print a is %d\n",ptr2struct->a);
    //free(ptr2struct);
    printf("value of ptr in funct2 is %p\n", ptr2struct);
    return 1; //success
}


int funct1(){

    struct1_t *ptr2struct = NULL;
    ptr2struct = malloc(sizeof(*ptr2struct));
    ptr2struct->a = 5;
    printf("value of ptr before used is %p", ptr2struct);
    if (funct2(ptr2struct) == 0) {
        goto error;
    }
    free(ptr2struct);

    printf("value of ptr in funct1 after freed is is %p\n", ptr2struct);
    return 1;

error:
    if(ptr2struct) free(ptr2struct);
    return 0;
}

У меня есть функция 1, которая вызывает функцию 2, и после использования выделенного указателя в funct1 я пытаюсь освободить указатель. И я создаю случай, когда если возвращаемое значение в funct2 не равно 1, попробуйте еще раз освободить указатель.

Мой вопрос ниже

какая практика лучше, если я должен освободить память в funct2 (после ее передачи) или в funct1 (после того, как я закончу получение возвращаемого значения funct1) Во-вторых, правильно ли это сделать ошибку goto и ошибку:

if(ptr2struct) free(ptr2struct); 

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

Ответ 1

1) Должен ли я освободить его в вызывающей функции или в вызываемой функции?

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

2) Правильно ли делать ошибку "goto"?

Да! Перепрыгивая в одно место в конце функции, вы избегаете дублирования кода, освобождающего ресурсы. Это обычная модель и не так уж плоха, так как "goto" просто служит своего рода "возвратным" утверждением и не делает ничего из своего действительно сложного и злого материала, для которого он более известен.

//in the middle of the function, whenever you would have a return statement
// instead do
return_value = something;
goto DONE;

//...

DONE:
    //resorce management code all in one spot
    free(stuff);
    return return_value;

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

Другой способ, с которым другие языки должны иметь дело с этим, - это, наконец, блоки.

3) Могу ли я увидеть, освобожден ли указатель?

К сожалению, вы не можете. То, что некоторые люди делают, это установить значение переменной указателя в NULL после его освобождения. Это не повредит (поскольку его старое значение не должно использоваться после его освобождения в любом случае), и у него есть свойство nice, что освобождение нулевого указателя указано как не-op.

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

Ответ 2

Вызов free() по указателю не меняет его, только отмечает память как бесплатную. Ваш указатель будет по-прежнему указывать на то же место, которое будет содержать одно и то же значение, но теперь vluae может быть перезаписано в любое время, поэтому вам не следует использовать указатель после его освобождения. Чтобы убедиться, что рекомендуется всегда указывать указатель на NULL после его освобождения.

Ответ 3

Мой вопрос ниже

какая практика лучше, если я должен освободить память в funct2 (после ее передачи) или в funct1 (после того, как я закончу получение возвращаемого значения funct1)

Это вопрос "собственности". Кому принадлежит выделенная память. Как правило, это должно решаться на основе дизайна вашей программы. Например, единственной целью func1() может быть выделение только памяти. То есть, в вашей реализации func1() является функцией распределения памяти, а затем функция "вызова" использует память. В этом случае право собственности на освобождение памяти связано с вызывающим абонентом func1 и NOT с func1().

Во-вторых, правильно ли это сделать ошибку goto и ошибку: Использование "goto", как правило, неодобрительно. Это вызывает беспорядок в коде, который можно просто избежать. Однако я говорю "вообще". Бывают случаи, когда goto может быть спокойным и полезным. Например, в больших системах конфигурация системы является большим шагом. Теперь представьте, что вы вызываете одну функцию Config() для системы, которая выделяет память для своих различных структур данных в разных точках функции, например

   config()
     {
       ...some config code...
       if ( a specific feature is enabled)
       {
         f1 = allocateMemory();
         level = 1;
       }
       ....some more code....
       if ( another feature is enabled)
       {
         f2 = allocateMemory();
         level = 2;
       }

       ....some more codee....
      if ( another feature is enabled)
      {
        f3 = allocateMemor();
        level =3;
      }

      /*some error happens */
       goto level_3;


     level_3: 
         free(f3);
     level_2:
         free(f2);
     level_1:
         free(f1);
}

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

Однако достаточно сказать, что в вашем примере goto легко можно избежать, и его следует избегать.

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

Легко. Установите освобожденную память как NULL. Другим преимуществом, помимо упомянутого MK, является то, что передача NULL-указателя на свободный вызывает NOP, т.е. Операция не выполняется. Это также поможет избежать любых проблем с двойным удалением.

Ответ 4

То, что я собираюсь поделиться, - это мои собственные методы развития в C. Они НЕТ - это единственный способ организовать себя. Я просто очерчиваю путь не так.

Хорошо, поэтому во многих отношениях "С" - это свободный язык, поэтому многие дисциплины и строгости исходят от себя как разработчика. Я занимаюсь разработкой в ​​ "С" более 20 лет профессионально, мне очень редко приходилось исправлять любое программное обеспечение профессионального уровня, которое я разработал. Хотя довольно много успеха можно отнести к опыту, справедливый кусок его коренится в последовательной практике.

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

  • Если у меня есть структура, которая используется во всем программном обеспечении, я пишу create/destroy; Функции init/done для него:

     struct foo * init_foo(); 
     void done_foo(struct foo *);
    

    и выделять и де-распределять структуру в этих функциях.

  • Если я манипулирую элементами структуры непосредственно по всей программе, тогда не набирайте ее. Я беру боль за использование ключевого слова struct в каждой декларации, чтобы я знал ее как структуру. Этого достаточно, когда порог боли НЕ так много, что я буду раздражаться им.: -)

  • Если я нахожу, что структура действует ОЧЕНЬ очень похожа на объект, тогда я решаю манипулировать элементами структуры STRICTLY через непрозрачный API; тогда я определяю его интерфейс через функции set/get type для каждого элемента, я создаю "декларацию вперед" в файле заголовка, используемом каждой другой частью программы, создавая непрозрачный typedef для указателя структуры и только объявляю фактическая структура в файле реализации структуры API.

foo.h:

struct foo;
typedef struct foo foo_t;

void set_e1(foo_t f, int e1);
int  get_ei(foo_t f);

int  set_buf(foo_t f, const char *buf);
char * get_buf_byref(foo_t f)
char * get_buf_byval(foo_t f, char *dest, size_t *dlen);

foo.c:

#include <foo.h>

struct foo {
     int e1; 
     char *buf;
     ...
};

void set_e1(foo_t f, int e1) { 
        f->e1 = e1;
}

int  get_ei(foo_t f) { return f->e1; } 

void set_buf(foo_t f, const char *buf) {
     if ( f->buf ) free ( f->buf );
     f->buf = strdup(buf);
}

char *get_buf_byref(foo_t f) { return f->buf; } 
char *get_buf_byval(foo_t f, char **dest, size_t *dlen) {
    *dlen = snprintf(*dest, (*dlen) - 1, "%s", f->buf); /* copy at most dlen-1 bytes */ 
    return *dest;
}
  • Если связанные структуры очень сложны, вы можете даже захотеть реализовать указатели функций прямо в базовую структуру, а затем предоставить фактические манипуляторы в определенных расширениях этой структуры.

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

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

Надеюсь, что это поможет.