简体   繁体   中英

Single linked list in C

I am trying to write a singly-linked list in C. So far, I just get segmentation faults . I am probably setting the pointers wrong, but I just couldn't figure how to do it correctly.

The list should be used for "processors" sorted from highest priority (at the beginning of the list) to lowest priority (at the end of the list). Head should point to the first element, but somehow I am doing it wrong.

First of all here is the code:

struct process {
    int id;
    int priority;
    struct process *next;
}

struct process *head = NULL;

void insert(int id, int priority) {
    struct process * element = (struct process *) malloc(sizeof(struct process));

    element->id = id;
    element->priority = priority;

    while(head->next->priority >= priority)
        head = head->next;

    element->next = head->next;
    head->next = element;
    // I put here a printf to result, which leads to segmenatition fault
    // printf("%d %d\n", element->id, element->priority);
}

/* This function should return and remove element with the highest priority */
int pop() {
    struct process * element = head->next;
    if(element == NULL)
        return -1;
    head->next = element->next;
    free(element);
    return element->id;
}
/* This function should remove a element with a given id */
void popId(int id) {
    struct process *ptr = head;
    struct process *tmp = NULL;

    while(prt != NULL) {
        if(ptr->id == id) {
            ptr->next = ptr->next->next;
            tmp = ptr->next;
        } else {
            prt = ptr->next;
        }
     }
    free(tmp);
}

Unfortunately, I could not try out pop() and popId() due to the segmentation fault.

May anyone tell me what I am doing wrong?

EDIT: Now, I edited the insert function. It looks like this:

void insert(int id, int priority) {
    struct process * element = (struct process *) malloc(sizeof(struct process));
    struct process * temp = head;

    element->id = id;
    element->priority = priority;

    if(head == NULL) {
        head = element; // edited due to Dukeling
        element->next = NULL;
    } else {

        while(temp->next != NULL && temp->next->priority >= priority)
            temp = temp->next;

        element->next = head->next;
        head->next = element;
    }
    // I put here a printf to result, which leads to segmenatition fault
    // printf("%d %d\n", element->id, element->priority);
}

But I still get segmentation fault for pop() and popId(). What did I miss here?

You don't check if head is NULL in insert .

You actually don't check if head is NULL in any function. You should, unless you want to put some dummy element on head , to simplify the code.

For insert :

About these lines:

while(head->next->priority >= priority)
    head = head->next;
  • If head is NULL , that's not going to work. This may not actually be a problem if head can never be NULL for whichever reason (eg it has a dummy element as gruszczy mentioned).

  • You're changing head , thus you're getting rid of the first few elements every time you insert. You probably need a temp variable.

  • You need to also have a NULL check in case you reach the end of the list.

So, we get:

struct process *temp = head;
while (temp->next != NULL && temp->next->priority >= priority)
    temp = temp->next;

For pop :

If the first element isn't a dummy element, then you should be returning the ID of head , not head->next (and you were trying to return a value of an already freed variable - this is undefined behaviour).

if (head == NULL)
    return -1;
int id = head->id;
struct process *temp = head;
head = head->next;
free(temp);
return id;

For popId :

  • You're checking ptr 's ID, but, if it's the one we're looking for, you're removing the next element rather than ptr . You should be checking the next one's ID.

  • head == NULL would again need to be a special case.

  • The free should be in the if-statement. If it isn't, you need to cater for it not being found or finding multiple elements with the same ID.

  • You should break out of the loop in the if-statement if there can only be one element with that ID, or you want to only remove the first such element.

I'll leave it to you to fix, but here's a version using double-pointers.

void popId(int id)
{
    struct process **ptr = &head;

    while (*ptr != NULL)
    {
        if ((*ptr)->id == id)
        {
            struct process *temp = *ptr;
            *ptr = (*ptr)->next;
            free(temp);
        }
        else
        {
            prt = &(*ptr)->next;
        }
    }
}

Note that the above code doesn't break out of the loop in the if-statement. This can be added if you're guaranteed to only have one element with some given ID in the list, or you want to just delete the first such element.

Your not checking your pointers before accessing their values for dereference. This will automatically lead to undefined behavior if the pointer is invalid (NULL or indeterminate). With each implementation below, note we don't access data via dereference unless the pointer is first-known as valid:

Implementation: insert()

void insert(int id, int priority) 
{
    struct process **pp = &head;
    struct process *element = malloc(sizeof(*element);
    element->id = id;
    element->priority = priority;

    while (*pp && (*pp)->priority >= priority)
        pp = &(*pp)->next;

    element->next = *pp;
    *pp = element;
}

Implementation: pop()

Your pop() function appears to be designed to return the popped value. While this isn't entirely uncommon it has the undesirable side-effect of having no mechanism for communicating to the caller that the queue is empty without a sentinel-value of some sort (such as (-1) in your case. This is the primary reason most queues have a top() , pop() , and isempty() functional interface. Regardless, assuming (-1) is acceptable as an error condition:

int pop()
{
    struct process *tmp = head;
    int res = -1;
    if (head)
    {
        head = head->next;
        res = tmp->id;
        free(tmp);
    }
    return res;
}

Implementation: popId()

Once again, looking for a specific node can be accomplished with a pointer-to-pointer in a fairly succinct algorithm, with automatic updating done for you due to using the actual physical pointers rather than just their values:

void popId(int id) 
{
    struct process ** pp = &head, *tmp = NULL;
    while (*pp && (*pp)->id != id)
        pp = &(*pp)->next;

    if (*pp)
    {
        tmp = *pp;
        *pp = tmp->next;
        free(tmp);
    }
}

I strongly advise stepping through each of these with a debugger to see how they work, particularly the insert() method, which has quite a lot going on under the covers for what is seemingly a small amount of code.

Best of luck

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM