简体   繁体   中英

C Quicksort (linked list) segmentation fault

I have to create a quicksort on a linked list (in C). I have my first and last pointer an the pivot (in this code it's the first element of the list). The structs I have to use:

typedef struct list_element list_element;

struct list_element {
    char *password;
    int count;
    list_element* next;
};

typedef struct list list;

struct list {
    list_element* first;
    list_element* last;
};

I have a file with 100 passwords and counts. Like this: password1 123 (next line) password2 435 (next line) password3 133 ... The passwords have to be sorted (according to their count) in the end of this programm. There isn't needed any extra memory allociation for the left and right lists because I only have to use the next pointers. (That's what the hint in the exercise says.)

The given main-function:

int main(int argc, char** args)
{
    if (argc != 2)
    {
        printf("Nutzung: %s <Dateiname>\n",args[0]);
        return 1;
    }
    list mylist;
    init_list(&mylist);
    read_data(args[1],&mylist);
    qsort_list(&mylist);
    printf("Sortierte Liste:\n");
    print_list(&mylist);
    free_list(&mylist);
    return 0;
}

I have initialized my list:

void init_list(list* mylist)
{
    mylist->first = NULL;
    mylist->last = NULL;
}

And insert a new element at end (passwort = passwords in file, hauefigkeit = counts in file):

void insert_list(list_element* le, list* mylist)
{
    if (mylist->first != NULL) {
        le->next = mylist->last;
        mylist->last = le;
        le->next= NULL;
    }
    else {
        mylist->last->next = le;
        mylist->last = le;
        mylist->last->next = NULL;
    }
}

Read data from file:

void read_data(char* filename, list* mylist)
{
    FILE *file_in = fopen(filename, "r"); 

    if (file_in == NULL) {
        perror("Could not open input file!");
        exit(1);
    }

    char buffer[999] = "0";

    char *passwort = (char*) calloc(1,sizeof(passwort));
    int haeufigkeit = 0;

    while (fgets(buffer, sizeof(buffer), file_in) != NULL) {
        sscanf(buffer, "%s %d", passwort, &haeufigkeit);

        list_element* le = (list_element*)calloc(1,sizeof(list_element));

        for(int i = 0; i <=100; i++) {
            le->password[i] = passwort[i];
        }
        le->count = haeufigkeit;
        le->next = NULL;

        insert_list(le, mylist);
    }
    fclose(file_in);
}

Partition of the list:

list_element* partition( list* input, list* left, list* right )
{
    list_element* pivot = NULL;
    if (input->first != NULL) {
        list_element* temp;
        pivot = input->first;
        input->first = input->first->next;
        pivot->next = NULL;
        left->first = NULL;
        right->first = NULL;

        while (input->first != NULL) {
            if((pivot->count)>(input->first->count)){
                temp=input->first->next;
                insert_list(input->first, left);
                input->first=temp;
            } 
            else {
                temp = input->first->next;
                insert_list(input->first, right);
                input->first = temp;
            }
        }
    }
    return pivot;
}

The actual quicksort:

void qsort_list(list* mylist)
{
    if(mylist->first == mylist->last){
    }

    else{
        list* left = calloc(1,sizeof(list));
        list* right= calloc(1,sizeof(list));
        list_element* pivot = partition(mylist, left, right);

        qsort_list(left);
        qsort_list(right);

        if(left->first == NULL){
            mylist->first = pivot;
        }
        else{
            mylist->first = left->first;
            left->last->next = pivot;
        }

        if(right->first == NULL){
            pivot->next = NULL;
            mylist->last = pivot;
        }
        else{
            pivot->next = right->first;
            mylist->last = right->last;
        }

        free(right);
        free(left);

    }
}

In the end print list:

void print_list(list* mylist)
{
    list_element *elem = mylist->first;
    while (elem != NULL) {
        printf("%s %d\n", elem->password, elem->count);
        elem = elem->next;
    }     
}

And free list:

void free_list(list* mylist)
{
    list_element *current;
    list_element *second;
    current = mylist->first;
    while (current != NULL) {
        second = current->next;
        free(current);
        current = second;
    }
}

Syntax should be ok. GCC (c99, Wall) compiles without any problems.

But there is an segmentation fault. I have been searching for hours now and I have no idea where the problem could be. Maybe you can help me with this problem.


After the first two answers there isn't any segmentation fault. But still have a problem with read_data function. The program can't read the passworts correctly. Maybe i misunderstood you answers in relation to the read function. That's the current function:

void read_data(char* filename, list* mylist)
{
    FILE *file_in = fopen(filename, "r"); 

    if (file_in == NULL) { 
        perror("Could not open input file!");
        exit(1);
    }

    char buffer[999] = "0";

    int haeufigkeit = 0;

    while (fgets(buffer, sizeof(buffer), file_in) != NULL) {

        char passwort[100];

        sscanf(buffer, "%s %d", passwort, &haeufigkeit);

        list_element* le = (list_element*)    

    calloc(1,sizeof(list_element));


        le->password = passwort;
        le->count = haeufigkeit;
        le->next = NULL;

        insert_list(le, mylist);
    }
    fclose(file_in);
}

As Leonardo Alves Machado pointed out, the first reflex when having trouble with a C/C++ program is to run it with a debugger like gdb . Here is the basics:

gcc -g main.c -o main
gdb main
(gdb) run

Note the -g compilation flag: this will add debug information to the executable.


In read_data , the lines

for(int i = 0; i <=100; i++) {
    le->password[i] = passwort[i];
}

really bug me. You allocate space for passwort (which you never free by the way) and try to copy it to le->password , which is a simple pointer (no allocated space). What you actually need is to make le->password point to passwort , ie

le->password = passwort;

In free_list , don't forget to deallocate the passwort space before deallocating the list_element space with:

while (current != NULL) {
    second = current->next;
    free(current->password);
    free(current);
    current = second;
}

One of the first issues your program encounters is that read_data() does not allocate enough space for passwort . It's unclear, actually, why you are dynamically allocating this at all, but given that you are doing so, sizeof(passwort) is the size of one pointer to char (since that's what passwort is) -- probably either 4 or 8 bytes. Later, you seem to assume that the allocated space is 100 bytes long when you (attempt to) copy its contents into a list element. Why not simply declare it as a 100-char array?

char passwort[100];

Indeed, if you also declare list_element.passwort the same way then your password-copying code inside the loop will be correct, albeit a bit non-idiomatic.

As it is, that code is problematic, as @Derlin observes. His proposed solution is incorrect, however; you must not make the list elements point to the local passwort as long as that is allocated only once for the whole routine. Then all list elements will have the same password string, which is not what you want. If you want your list elements to contain pointers to the passwords, as they do now, then you'll want to move the declaration and allocation of passwort inside the loop, so that you get separate password space allocated for each list element. Then the suggestion to assign le->password = passwort would be correct.


Another early issue is that your insert_list() function is badly broken.

Consider first what happens when you try to insert an element into an empty list, as initialized by init_list() . The list's next and last members will both be null, and insert_list() will therefore attempt to execute this code:

    mylist->last->next = le;
    mylist->last = le;
    mylist->last->next = NULL;

Observe that mylist->last is null, therefore the first line invokes undefined behavior by attempting to dereference a null pointer. A segmentation fault is an eminently plausible observed result. You might fix that by changing the first of those lines to

    mylist->next = le;

Now consider what happens when you try to insert into a non-empty list. In that case, you execute these lines:

    le->next = mylist->last;
    mylist->last = le;
    le->next= NULL;

Since your intention is to insert the new element at the end (ie to append it), it is odd that you set the new element's next pointer to the list's last element. It is especially odd that you later overwrite that value with NULL . You seem to have it backward: you want to set the initial last element to point to the new element as its next element, not the other way around:

    mylist->last->next = le;

Indeed, that's exactly the code that was wrong for the empty-list case, but it's fine when the list is non-empty.

Overall, your function also suffers from an odd lack of parallelism and some hidden code duplication. I'd probably write the overall function something more like this:

void append_to_list(list_element* le, list* mylist)
{
    le->next= NULL;
    if (mylist->first != NULL) {
        mylist->last->next = le;
        mylist->last = le;
    }
    else {
        mylist->first = le;
        mylist->last = le;
    }
}

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