Проблема с PriorityQueue в C - PullRequest
       2

Проблема с PriorityQueue в C

1 голос
/ 14 октября 2010

Я пытаюсь написать PriorityQueue для проекта переменного тока.Программа вылетает, когда я пытаюсь удалить элементы из очереди;однако я думаю, что проблема связана с тем, как я добавляю элементы, например, если я пытаюсь получить доступ к первому элементу списка после добавления третьего элемента, я также получаю сбой.

Файл заголовка:

#ifndef PQUEUE_H_INCLUDED 
#define PQUEUE_H_INCLUDED

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

//Data structure for holding one element in pqueue
typedef struct _e {
    void *data;
    size_t datalen;
    int priority;
    struct _e *next;
} ELEMENT;

//data structure for the whole pqueue
typedef struct {
    ELEMENT *head;          //reference to first element 
    ELEMENT *tail;          //reference to last element
    ELEMENT *beforefirst;   //dummy element before first element;
    int elements;
} PQUEUE;


extern PQUEUE*  queue_new(void);
extern void     queue_free(PQUEUE *);
extern void     queue_add_end(PQUEUE *, void *, size_t);
extern void     queue_add_priority(PQUEUE *, void *, size_t,int);
extern void*    queue_remove(PQUEUE *);
extern bool     queue_has_next(PQUEUE *);
extern int      queue_size(PQUEUE *);


#endif 

Код PriorityQueue:

#include "pqueue.h"

PQUEUE *queue_new(void) {
    PQUEUE *pq = malloc(sizeof(PQUEUE));
    if (pq == NULL) {
        perror("queue_new");
        exit(EXIT_FAILURE);
    }
    pq->head = NULL;
    ELEMENT *newelement;
    newelement = calloc(1,sizeof(ELEMENT));
    pq->beforefirst = newelement;
    pq->beforefirst->next = pq->head;
    pq->tail = NULL;
    pq->elements = 0;

    return pq;
}

void queue_free(PQUEUE *pq) {
    ELEMENT *this, *save;
    this = pq->head;
    while(this!= NULL) {
        save = this;
        this = this->next;
        free(save->data);
        free(save);
    }
    free(pq);
}


void queue_add_priority(PQUEUE *pq, void *data, size_t datalen,int priority) {
    ELEMENT *newelement;
    newelement = calloc(1,sizeof(ELEMENT));
    if (newelement == NULL) {
        perror("queue_add");
        exit(EXIT_FAILURE);
    }
    newelement->data = malloc(datalen);
    newelement->priority = priority;
    if(newelement->data == NULL) {
        perror("queue_add");
        exit(EXIT_FAILURE);
    }
    memcpy(newelement->data,data,datalen);
    newelement->datalen = datalen;
    newelement->next = NULL;
    //sets pointer at beforefirst element and iterates through queue until ptr->next
    // priority is greater than newlement priority, or until end of queue.
    ELEMENT *ptr = pq->beforefirst;
    while (ptr->next != NULL) {
        if (ptr->next->priority > priority) break;
        ptr = ptr->next;
    }
    if (ptr == pq->beforefirst) {
        pq->head = newelement;
    }
    if (ptr->next == NULL) {
        pq->tail = newelement;
    }
    newelement->next = ptr->next;
    ptr->next = newelement;


    //ERROR HERE
    //void* d;
    //d = pq->head->data;
    pq->elements++;
}

void* queue_remove(PQUEUE *pq) {
    //ERROR HERE
    void* item = pq->head->data;
    pq->head = pq->head->next;
    pq->elements--;
    return item;
}

bool queue_has_next(PQUEUE *pq) {
    return !(pq->elements == 0);
}

В основном, появляется ошибка, когда я пытаюсь получить доступ к данным pq-> head-> после добавления третьего элемента - я сузилсяэто до областей, прокомментированных // ОШИБКА ЗДЕСЬ.Мне кажется странным, потому что добавление третьего элемента должно работать так же, как и добавление второго.Также ни pq-> head == NULL, ни pq-> head> data == NULL.

Ответы [ 4 ]

3 голосов
/ 14 октября 2010

Я вижу следующие проблемы:

  • queue_free не освобождает память для объекта beforeFirst.
  • add_priority не освобождает элемент, еслираспределение данных не удается.Это не имеет большого значения, так как вы выходите, но было бы неплохо, если вы когда-нибудь решите вернуть ошибку (другими словами, это остановит утечку памяти).

ОднакоЯ протестировал этот код, вставив новый элемент, элемент перед этим, затем элемент в конце, и это выглядит нормально.Какие значения приоритетов вы вставляете (по порядку)?

И вы можете разместить код, который вызывает этот код.Вполне возможно, что это может быть проблема повреждения памяти, не связанная с этим фактическим кодом.


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

На самом деле я не проверял это, кроме как в моем программном обеспечении, но он должен (надеюсь) работать нормально:

typedef struct _e {
    void *data;
    size_t datalen;
    int priority;
    struct _e *next;
} ELEMENT;

typedef struct {
    ELEMENT *head;          //reference to first element 
    ELEMENT *tail;          //reference to last element
    int elements;
} PQUEUE;

PQUEUE *queue_new(void) {
    PQUEUE *pq = malloc(sizeof(PQUEUE));
    if (pq == NULL) {
        perror("queue_new");
        exit(EXIT_FAILURE);
    }
    pq->head = pq->tail = NULL;
    pq->elements = 0;
    return pq;
}

void queue_free(PQUEUE *pq) {
    ELEMENT *this, *save;
    this = pq->head;
    while(this!= NULL) {
        save = this;
        this = this->next;
        free(save->data);
        free(save);
    }
    free(pq);
}

void queue_add_priority(PQUEUE *pq, void *data, size_t datalen, int priority) {
    ELEMENT *newelement;
    newelement = calloc(1,sizeof(ELEMENT));
    if (newelement == NULL) {
        perror("queue_add");
        exit(EXIT_FAILURE);
    }
    newelement->data = malloc(datalen);
    if(newelement->data == NULL) {
        perror("queue_add");
        free (newelement);
        exit(EXIT_FAILURE);
    }
    memcpy(newelement->data,data,datalen);
    newelement->datalen = datalen;
    newelement->priority = priority;
    newelement->next = NULL;

    // Inserting into empty list.
    if (pq->elements == 0) {
        pq->head = pq->tail = newelement;
        pq->elements = 1;
        return;
    }

    // Inserting beyond tail.
    if (pq->tail->priority <= priority) {
        pq->tail->next = newelement;
        pq->tail = newelement;
        pq->elements++;
        return;
    }

    // Inserting before head.
    if (pq->head->priority > priority) {
        newelement->next = pq->head;
        pq->head = newelement;
        pq->elements++;
        return;
    }

    // Otherwise, we're inserting somewhere in the middle.
    ELEMENT *ptr = pq->head;
    while (ptr->next->priority <= priority)
        ptr = ptr->next;
    newelement->next = ptr->next;
    ptr->next = newelement;
    pq->elements++;
}

void* queue_remove(PQUEUE *pq) {
    if (pq->elements == 0)        // added, just in case.
        return NULL;
    void* item = pq->head->data;
    pq->head = pq->head->next;
    pq->elements--;
    return item;
}

bool queue_has_next(PQUEUE *pq) {
    return (pq->elements > 0);    // better, IMNSHO.
}

Имейте в виду, что queue_add_priority делает копию памяти, чтобы вы могли передать еединамически распределяемая память или иным образом.Эта функция не несет ответственности за освобождение выделенной памяти, которую вы ей передаете.Если он распределяется динамически, вы все равно должны освободить его самостоятельно.Это было сделано таким образом, чтобы вы могли передавать в любую память любого типа.

С другой стороны, queue_remove просто передает вам выделенную память, чтобы вы были ответственность за освобождение, когда вы закончите.Полученная вами память будет всегда получена через malloc.

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

void queue_add_priority(PQUEUE *pq, void *data, size_t datalen, int priority) {
    ELEMENT *newelement;
    newelement = calloc(1,sizeof(ELEMENT));
    if (newelement == NULL) {
        perror("queue_add");
        exit(EXIT_FAILURE);
    }
    newelement->data = malloc(datalen);
    if(newelement->data == NULL) {
        perror("queue_add");
        free (newelement);
        exit(EXIT_FAILURE);
    }
    memcpy(newelement->data,data,datalen);

на:

void queue_add_priority(PQUEUE *pq, void *data, size_t datalen, int priority, int xfer) {
    ELEMENT *newelement;
    newelement = calloc(1,sizeof(ELEMENT));
    if (newelement == NULL) {
        perror("queue_add");
        exit(EXIT_FAILURE);
    }
    if (!xfer) {
        newelement->data = malloc(datalen);
        if(newelement->data == NULL) {
            perror("queue_add");
            free (newelement);
            exit(EXIT_FAILURE);
        }
        memcpy(newelement->data,data,datalen);
    } else {
        newelement->data = data;
    }

Другими словами, установите для конечного параметра значение trueесли данные были получены с помощью malloc, и вы соглашаетесь отказаться от ответственности за них - таким образом, функция просто принимает ваш блок памяти как есть.

В противном случае используйте false, и будет сделана копия.

0 голосов
/ 14 октября 2010

Вы должны исправить queue_add, чтобы также изменить before_first, если вставленный элемент находится на первой позиции.

Как правило, я бы не использовал before_first. Просто измените ваши циклы, чтобы проверить ptr на current == null и измените начальный элемент на первый.

Следует также устранить все другие проблемы.

НТН

Mario

0 голосов
/ 14 октября 2010

В дополнение к проблемам, которые paxdiablo упомянули , вы также забыли обновить beforefirst->next в случае, когда вы удаляете заголовок очереди. Вот что происходит:

Before queue_remove:

  beforefirst             head                tail
       |                   |                   |
       V                   V                   V
+-------------+     +-------------+     +-------------+
| placeholder | --> |      x      | --> |      y      | --> NULL
+-------------+     +-------------+     +-------------+



After queue_remove:

  beforefirst                             head   tail
       |                                    |      |
       V                                    V      V
+-------------+     +-------------+     +-------------+
| placeholder | --> |      x      | --> |      y      | --> NULL
+-------------+     +-------------+     +-------------+

Вам необходимо исправить queue_remove, чтобы beforefirst->next указывало на head->next, если head не равно NULL (иначе NULL).

0 голосов
/ 14 октября 2010

Вот шаблон доступа, который вызывает проблему.

PQUEUE *queue = queue_new();
int x0 = 0;
int x1 = 1;
queue_add_priority(queue, &x0, sizeof(x0), x0); //add1
queue_add_priority(queue, &x1, sizeof(x1), x1); //add2
queue_remove(queue);                            //remove
queue_add_priority(queue, &x0, sizeof(x0), x0); //add3
while (queue_has_next(queue))
    printf("%d\n", *(int *)queue_remove(queue));

Должны ли элементы с более высоким приоритетом появляться в голове?Этого не происходит в вашем коде, если это необходимо.

После первых двух добавлений счетчик равен двум, а элемент с более низким приоритетом находится во главе (0 1, количество: 2).Следующее удаление берет элемент head, уменьшающий количество.То, что осталось, это элемент с приоритетом 1 (1, количество: 1).Проблема заключается в добавлении еще одного элемента с приоритетом 0, который фактически ничего не добавляет в очередь, и счетчик все еще увеличивается (1, count: 2).Затем, так как очередь записала, что существует 2 элемента, а на самом деле их только 1, удаление в конце завершается неудачей.

Проблема заключается в обходе очереди.Более конкретно, с чего начать (указатель beforefirst) не обновляется после удаления.После удаления он все еще указывает на «удаленный» узел.По сути, все последующие добавления будут добавляться в конец ранее удаленного узла, оставляя фактическую очередь в несогласованном состоянии.Это одна из причин, по которой целесообразно последовательно освобождать память, когда она не нужна, и устанавливать указатель на NULL впоследствии.

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