Связанный список возвращает значения в неправильном порядке

#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) in slist_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 освобождает строковый буфер, а не освобождает узел!