it-swarm.com.ru

Свободная память выделена в другую функцию?

Я пытаюсь выучить C, и в настоящее время я пытаюсь написать базовую структуру данных стека, но я не могу понять, как правильно malloc/free.

Вот код, который я использовал (я просто публикую небольшую часть здесь, чтобы проиллюстрировать конкретную проблему, а не общий код, но сообщение об ошибке было сгенерировано просто путем запуска этого примера кода в valgrind)

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

typedef struct Entry {
    struct Entry *previous;
    int value;
} Entry;

void destroyEntry(Entry entry);

int main(int argc, char *argv[])
{
    Entry* Apple;
    Apple = malloc(sizeof(Entry));
    destroyEntry(*(Apple));
    return 0;
}

void destroyEntry(Entry entry)
{
    Entry *entry_ptr = &entry;
    free(entry_ptr);
    return;
}

Когда я запускаю его через valgrind с --leak-check=full --track-origins=yes, я получаю следующую ошибку:

==20674== Invalid free() / delete / delete[] / realloc()
==20674==    at 0x4028E58: free (vg_replace_malloc.c:427)
==20674==    by 0x80485B2: destroyEntry (testing.c:53)
==20674==    by 0x8048477: main (testing.c:26)
==20674==  Address 0xbecc0070 is on thread 1's stack

Я думаю, что эта ошибка означает, что функция destroyEntry не может изменять память, явно выделенную в main. Это правильно? Почему я не могу просто free памяти, которую я выделил в main в другой функции? (и это поведение как-то специфично для основного?)

65
Jeff Tratner

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

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

51
Oliver Charlesworth

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

void destroyEntry(Entry entry)
{
    Entry *entry_ptr = &entry;
    free(entry_ptr);
    return;
}

Ваша функция должна взять указатель (передавая по ссылке):

void destroyEntry(Entry *entry)
{
    free(entry);
}

Затем вместо destroyEntry(*(Apple)); вы просто вызываете destroyEntry(Apple);. Обратите внимание, что если с функцией destroyEntry нет другой функциональности, она избыточна, и лучше просто напрямую вызвать free(Apple).

37
LihO

Другие ответы здесь указывают на главную проблему - поскольку вы разыменовываете свой Apple, когда вызываете destroyEntry в main (), он передается по ссылке, создавая копию.

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

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

int main()
{
  Entry * Apple;
  Apple = malloc(sizeof(Entry));
  printf("Apple's address = %p", Apple);  // Prints the address of 'Apple'
  free(Apple);   // You know this will work
}

После этого вы заметите, что оператор printf () дал вам адрес, похожий на 0x8024712 (просто составляющий адрес в правом общем диапазоне), но ваш вывод valgrind дал 0x4028E58. Вы заметите, что они находятся в двух очень разных местах (на самом деле, "0x4 ..." находится в стеке, а не в куче, из которой выделяется malloc (), но я предполагаю, что если вы только начинаете, это пока для вас нет красного флажка), так что вы знаете, что пытаетесь освободить память из неправильного места, следовательно, "invalid free ()".

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

TL; DR - при возникновении ошибок, связанных с указателем, попробуйте распечатать адреса или найти их в своем любимом отладчике. Часто он, по крайней мере, указывает вам правильное направление.

Конечно, это не должно препятствовать публикации вашего вопроса в Stack Exchange. Сотни программистов, вероятно, выиграют, если вы сделаете это.

9
gkimsey