Освобождение указателей из других функций в C - PullRequest
10 голосов
/ 19 мая 2011

Рассмотрим код c:

void mycode() {
  MyType* p = malloc(sizeof(MyType));
  /* set the values for p and do some stuff with it */
  cleanup(p);
}


void cleanup(MyType* pointer) {
  free(pointer);
  pointer = NULL;
}

Я ошибаюсь, думая, что после вызова cleanup(p); содержимое p теперь должно быть NULL? cleanup(MyType* pointer) правильно ли освободит память?

Я кодирую свое задание в колледже и обнаруживаю, что отладчик все еще показывает, что указатель имеет адрес памяти вместо 0x0 (или NULL), как я ожидал.

Я считаю управление памятью в C очень сложным (надеюсь, это не только я). Кто-нибудь может пролить свет на то, что происходит?

Ответы [ 6 ]

18 голосов
/ 19 мая 2011

Да, это освободит память правильно.

pointer внутри функции очистки является локальной переменной;копия переданного значения хранится локально только для этой функции.

Это может добавить к вашей путанице, но вы можете отрегулировать значение переменной p с помощью метода mycode следующим образом:

void cleanup(MyType** pointer) {
  free(*pointer);
  *pointer = NULL;
}

В этом случае pointer сохраняет адрес указателя.Разыменовывая это, вы можете изменить значение, хранящееся по этому адресу.

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

10 голосов
/ 19 мая 2011

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

void cleanup(MyType **pointer) {
  free(*pointer);
  *pointer = NULL;
}

и назвать его так:

cleanup(&p);

Ваш код немногонемного идиоматично, можете ли вы объяснить немного лучше, почему вы хотите написать эту cleanup функцию?

7 голосов
/ 19 мая 2011

Да

Да

Да: Блок памяти, магически созданный malloc (3). Вы присвоили адрес этой памяти, но не саму память каким-либо значимым образом, указателю p, который является переменной auto в mycode().

Затем вы передаете p в cleanup() по значению, которое скопирует указатель и, используя локальную копию для cleanup(), освободит блок. cleanup() затем устанавливает свой собственный экземпляр указателя в NULL, но это бесполезно. После завершения функции параметр pointer перестает существовать.

Возвращаясь к mycode(), у вас все еще есть указатель p, содержащий адрес, но блок теперь находится в свободном списке и не очень полезен для хранения до повторного выделения.

Вы можете заметить, что вы все еще можете сохранять и считывать из *p,, но произойдут различные потери в нисходящем направлении, так как этот блок памяти теперь принадлежит библиотеке, и вы можете повредить ее структуры данных или данные будущий владелец блока malloc ().

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

3 голосов
/ 19 мая 2011

Это не будет работать, поскольку pointer в cleanup() является локальным, и, следовательно, присвоение ему NULL не отображается вызывающей функцией. Есть два распространенных способа решения этой проблемы.

  1. Вместо отправки указателя очистки, отправьте указатель на указатель. Таким образом измените cleanup() следующим образом:
void cleanup(MyType** pointer)
{
  free(*pointer);
  *pointer = NULL;
}

, а затем просто позвоните cleanup(&p).

  1. Второй довольно распространенный вариант - использовать макрос #define, который освобождает память и очищает указатель.

Если вы используете C ++, тогда существует третий способ, определяющий cleanup() как:

очистка void (указатель MyType & *) { // ваш старый код остается прежним }

1 голос
/ 19 мая 2011

Это не только ты.

cleanup() правильно очистит ваше выделение, но не установит указатель на NULL (который должен ИМХО рассматриваться как отдельный от очистки.) Данные , на которые указывает указатель , передаются cleanup() указателем, и free() редактируется правильно, но сам указатель передается по значению, поэтому, когда вы устанавливаете его на NULL, вы воздействуете только на локальную копию функции cleanup() указателя, а не исходный указатель.

Есть три способа обойти это:

  1. Использовать указатель на указатель.

    void cleanup(struct MyType **p) { free(*p); *p = NULL; }
    
  2. Использовать макрос.

    #define cleanup(p) do { free(p); p = NULL; } while(0)
    

    или (возможно, лучше):

    void cleanup_func(struct MyType *p) { /* more complicated cleanup */ }
    #define cleanup(p) do { cleanup_func(p); p = NULL; } while(0)
    
  3. Оставьте ответственность за установку указателей на NULL для вызывающей стороны. Это может избежать ненужных назначений и беспорядка кода или поломки.

1 голос
/ 19 мая 2011

Здесь есть два вопроса:

Я ошибаюсь, думая, что после очистки (p);называется, содержимое p теперь должно быть NULL?

Да, это неправильно.После вызова free память, указанная указателем, освобождается.Это не означает, что содержимое, на которое указывает указатель, имеет значение NULL.Кроме того, если вы ожидаете, что указатель p станет равным NULL в mycode, этого не произойдет, поскольку вы передаете copy из p в cleanup.Если вы хотите, чтобы p был равен NULL в mycode, то вам нужен указатель на указатель в cleanup, то есть подпись очистки будет cleanup(MyType**).

Второй вопрос:

Будет ли очистка (указатель MyType *) правильно освобождать выделение памяти?

Да, поскольку вы делаете free для указателя, возвращаемого malloc, память будет освобождена.

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