#c #memory-leaks #linked-list
#c #утечки памяти #связанный список
Вопрос:
Я (пытался) написать LinkedList, однако, когда я перебираю все элементы в списке, элементы выводятся в другом порядке, чем они вставлены.
Скажем, я вставляю их таким образом:
slist_insert(list, "red");
slist_insert(list, "green");
slist_insert(list, "blue");
slist_insert(list, "yellow");
slist_insert(list, "pink");
slist_insert(list, "purple");
slist_insert(list, "beige");
slist_insert(list, "white");
slist_insert(list, "black");
slist_insert(list, "brown");
slist_insert(list, "fuchsia");
slist_insert(list, "aqua");
slist_insert(list, "magenta");
Но в цикле это выдается вместо:
green
magenta
aqua
fuchsia
brown
black
white
beige
purple
pink
yellow
blue
red
Я не делал этого раньше, имейте в виду, так что есть очень большая вероятность, что этот код изобилует элементарными ошибками, связанными с алгоритмами связанных списков: http://codepad.org/Sl0WVeos
Код, как таковой, работает нормально, но есть несколько вещей, которые меня беспокоят:
- Получен неправильный порядок (как объяснено выше)
- Необходимо использовать макросы (есть ли более удобный способ сделать это?)
- Даже после вызова
slist_destroy
все еще происходит утечка памяти, и я не могу понять, откуда это происходит
Помощь действительно ценится!
Комментарии:
1. Это слишком много кода. Попробуйте отладить это самостоятельно дальше, но сначала запустите свой код с добавлением только одного элемента, затем только двух, затем только трех. Убедитесь, что ваше удаление работает во всех трех приведенных выше случаях. Вы должны иметь возможность отслеживать свои распределения с помощью pen amp; paper.
2. @Mat О, я отладил код как с помощью valgrind (с
--leak-check=full
, соответственно), так и с помощью gdb, но безрезультатно. Даже дополнительныйfree(list)
inslist_destroy
внутри цикла (перед назначением следующему списку) не закрывает утечку памяти. Отсюда и вопрос.
Ответ №1:
о неправильном порядке элементов
ваша логика slist_impl_insertl()
неверна.
давайте следовать вашему коду:
stringlist_t* slist_impl_insertl(stringlist_t* list, const char* str, unsigned int len)
{
stringlist_t* newnode;
if(list == NULL) // if the list is empty
{
newnode = slist_createl(str, len); // create a new item
list = newnode; // insert the new item at the start of the list
return list;
}
else // if the list is not empty
{
if(list->next == NULL) // if it contains only one item
{
list = slist_insertb(list, str, len); // insert a new item at the front of the list
return list;
}
else // if it contains more than one item
{
newnode = slist_createl(str, len); // create a new node
newnode->next = (struct stringlist_t*)list->next; // insert the new node just after the first item !?!.
list->next = (struct stringlist_t*)newnode;
return list;
}
}
return list; /* not reached */
}
таким образом, ваша процедура вставки не всегда вставляет новый узел в одно и то же место. иногда он вставляется в начале, иногда он вставляется на втором месте. это объясняет, почему элементы выводятся в неправильном порядке.
тривиальное исправление заключается в том, чтобы всегда вставлять новый узел в начало списка, тогда элементы будут выдаваться в обратном порядке. или вы можете перебирать список, пока не дойдете до конца ( list->next == NULL
) , и вставить новый элемент после этого последнего элемента:
stringlist_t* slist_impl_insertl(stringlist_t* list, const char* str, unsigned int len)
{
stringlist_t *iter;
if(list == NULL)
{
list = slist_createl(str, len);
}
else
{
// find the last ist item
iter = list;
while(iter->next!=NULL)
iter = iter->next;
// insert the new item at the end of the list
iter->next = slist_createl(str,len);
}
return list;
}
об использовании макросов
если список пуст ( list == NULL
), ваша процедура вставки изменит список, чтобы сделать его первым элементом. макрос заботится о переназначении измененного списка. если вы не хотите использовать макросы, вам нужно передать аргумент list в качестве указателя, чтобы вы могли изменять его непосредственно в процедуре вставки.
(парень, который написал код в первую очередь, сделал так, что он может вставить элемент в любом месте в середине списка без необходимости писать специальные процедуры для этого)
вот возможная реализация slist_insert()
без использования макроса:
void slist_insert(stringlist_t** list, const char* str)
{
*list = slist_impl_insertl(*list, str);
}
используя эту реализацию, вы должны изменить способ вставки элементов в список:
slist_insert(amp;list, "red"); // note the use of 'amp;'
об утечке памяти
процедура уничтожения освобождает строки, хранящиеся в каждом элементе, это нормально. но каждый элемент также динамически распределяется, поэтому их также необходимо освободить! вы должны временно сохранить указатель списка, перейти к следующему элементу, затем освободить сохраненный указатель, пока не дойдете до конца списка.
void slist_destroy(stringlist_t* list)
{
stringlist_t *temp;
while(list != NULL)
{
// free the data contained in the current list item
free(list->data);
// save the pointer to the next item
temp = slist_next(list);
// free the current item
free(list);
// continue with the next item
list = temp;
}
}
Комментарии:
1. Мне жаль читать, что вы все еще считаете, что это домашнее задание, на самом деле.
2. Я соответствующим образом использовал ваши рекомендации, и вот результат: codepad.org/E44XRoLZ . Большое вам спасибо за вашу любезную помощь, это действительно ценится.
3. @hiobs: хорошая переписка. однако остается один маленький момент: in
slist_destroy()
, который вы проверяетеNULL
перед освобождением указателя (if(head->data != NULL) free(head->data);
. это избыточно,free()
позаботится оNULL
самих указателях.4. спасибо за предупреждение, но эта проверка предназначена для случаев, когда
free
не проверяется, является ли указательNULL
или нет. Я считаю, что раньше это было / имеет место в Windows (и VisualStudio), но считаю это утверждение нереальным.
Ответ №2:
Здесь я даю вам связанный список, сделанный мной, который, возможно, поможет вам понять эту структуру данных (я добавляю функции для вставки и удаления узлов)
void insert(LISTNODEPTR* sPtr, char item)
{
LISTNODEPTR previousPtr,currentPtr,newPtr;
newPtr = malloc(sizeof(LISTNODE));
if(newPtr != NULL)
{
newPtr->value = item;
newPtr->nextPtr = NULL;
currentPtr = *sPtr;
previousPtr = NULL;
while(currentPtr != NULL amp;amp; currentPtr->value < item)
{
previousPtr = currentPtr;
currentPtr = currentPtr->nextPtr;
}
if(previousPtr == NULL)
{
newPtr->nextPtr = *sPtr;
*sPtr = newPtr;
}
else
{
previousPtr->nextPtr = newPtr;
newPtr->nextPtr = currentPtr;
}
}
else
{
printf("No memory availablen");
}
}
void delete(LISTNODEPTR* sPtr,char item)
{
LISTNODEPTR currentPtr,previousPtr,tempPtr;
if((*sPtr)->value == item)
{
tempPtr = *sPtr;
*sPtr = (*sPtr)->nextPtr;
free(tempPtr);
}
else
{
previousPtr = NULL;
currentPtr = *sPtr;
while(currentPtr != NULL amp;amp; currentPtr->value != item)
{
previousPtr = currentPtr;
currentPtr = currentPtr->nextPtr;
}
tempPtr = currentPtr;
previousPtr->nextPtr = currentPtr->nextPtr;
free(tempPtr);
}
}
Ответ №3:
newnode = slist_createl(str, len);
newnode->next = (struct stringlist_t*)list->next;//1)
list->next = (struct stringlist_t*)newnode;//2)
return list;
состояние списка, когда:
зеленый-> красный-> NULL
1) newnode-> red-> NULL
2) зеленый -> newnode-> красный-> NULL
newnode всегда вставляется между зеленым (зеленый после) и красным.
и,
slist_destroy
slist_destroy освобождает строковый буфер, а не освобождает узел!