Hi everyone,

I'm working on an assignment that takes a record of student ID, student last and first name and their score, using array of pointers, qsort to sort in alphabetical order, typedef structs, malloc and calloc, etc. and the program prompts those records and it is stored in dynamic memory. This means that it is stored in a dynamically-allocated structure & the address of that structure is stored in the dynamic array of pointers. After that, the program will continue to prompt until the user enters a EOF key to return to the main menu and will have the option to display those records.

The error in my code that I'm trying to fix is that after I enter a valid record the second time through and return to the main menu using EOF key and try to display it, it will get a segmentation fault. However, there is no error when I enter and display only one valid record.

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

#define NAMESIZE 20
#define BLOCK 20
#define BUFSIZE 512

typedef struct {
    char last[NAMESIZE];
    char first[NAMESIZE];
} name;

typedef struct {
    int id;
    name name;
    int score;
} record;

typedef struct {
    record **data;
    size_t nalloc;
    size_t nused;
} record_list;

void list_init(record_list *list);
void list_destroy(record_list *list);
int list_insert(record_list *list, const record *rec);
int prompt_records(record *rec);
void insert_records(record_list *list);
int cmp_name(const void *p, const void *q);
int cmp_ID(const void *p, const void *q);
void display_ID(record_list *list);
void display_name(record_list *list);

int main(void) {
    char input[BUFSIZE];
    int choice = 0;

    record_list * list = (record_list *) malloc(sizeof(record_list));
    if(list->data == NULL) {
        fprintf(stderr, "No memory allocated.\n");
    }
    list_init(list);

    printf("------------------\n");
    printf("Starting program\n");
    printf("------------------\n");

    while(1) {
        fprintf(stderr, "\n1. Enter records\n");
        fprintf(stderr, "2. Display records by sorted ID\n");
        fprintf(stderr, "3. Display records by sorted name\n");
        fprintf(stderr, "4. Quit\n");
        fprintf(stderr, "Enter selection: ");

        if(!fgets(input, BUFSIZE, stdin)) {
            clearerr(stdin);
            return 0;
        }
        if(sscanf(input, "%d", &choice) == 1) {
            switch (choice) {
            case 1:
                insert_records(list);
                break;
            case 2:
                display_ID(list);
                break;
            case 3:
                display_name(list);
                break;
            case 4: 
                printf("Quitting program...\n");
                list_destroy(list);
                return 0;
            default:
                printf("Invalid input. Reenter again.\n");
                break;
            } 
        } else {
            printf("Invalid input. Reenter again.\n");
        }
    }
    return 0;
}

void list_init(record_list *list) {
    size_t nalloc = BLOCK;
    size_t nused = 0;
    record ** data = (record **) malloc(BLOCK * sizeof(record*));

    if(data == NULL) {
        fprintf(stderr, "Out of memory\n");
        exit(0);
    } 

    list->data = data;
    list->nalloc = nalloc;
    list->nused = nused;
}

void list_destroy(record_list *list) {
    size_t i = 0;

    for(i = 0; i < list->nused; i++) {
        free(list->data[i]);

        #ifdef DEBUG
            fprintf(stderr, "@\n");
        #endif
    }

    free(list->data);
    #ifdef DEBUG
        fprintf(stderr, "@\n");
    #endif  
}

int list_insert(record_list *list, const record *rec) { 
    record **temp;
    int nused = list->nused;

    if(list->nalloc == list->nused) {
        temp = realloc(list->data, (list->nalloc + BLOCK) * sizeof(record*));

        #ifdef DEBUG
            fprintf(stderr, "#\n");
        #endif

        /* if we cannot allocate temp */
        if(temp == NULL) {
            fprintf(stderr, "Out of memory\n");
            return 0;
        }

        list->data = temp;
        list->nalloc += BLOCK;
    }
    list->data[nused] = (record*) malloc(sizeof(record));

    #ifdef DEBUG
        fprintf(stderr, "%\n");
    #endif

    list->data[nused]->id = rec->id;
    strncpy(list->data[nused]->name.last, rec->name.last, NAMESIZE);
    strncpy(list->data[nused]->name.first, rec->name.first, NAMESIZE);
    list->data[nused]->score = rec->score;

    list->nused++;

    return 1;

}
int prompt_records(record *rec) {

    char tempID[BUFSIZE];
    char last[NAMESIZE];
    char first[NAMESIZE];
    char tempScore[BUFSIZE];

    while(1) {
        fprintf(stderr, "Enter an ID between 1000000 & 9999999: ");
        if(!fgets(tempID, BUFSIZE, stdin)) {
            clearerr(stdin);
            return 0;
        }
        if(sscanf(tempID, "%d", &rec->id) == 1) {
            if(rec->id == 0) {
                return 0;
            }
            if(rec->id >= 1000000 && rec->id <= 9999999) {
                break;
            }           
        }
        fprintf(stderr, "Invalid ID format.\n");
    }

    while(1) {

        fprintf(stderr, "Enter a valid last name: ");
        if(!fgets(last, NAMESIZE, stdin)) {
            clearerr(stdin);
            return 0;
        }
        if(sscanf(last, "%s", rec->name.last) == 1) {
            if(strlen(rec->name.last) < NAMESIZE) {
                break;
            }
        } 
        fprintf(stderr, "Incorrect last name.\n");
    }
    while(1) {

        fprintf(stderr, "Enter a valid first name: ");
        if(!fgets(first, NAMESIZE, stdin)) {
            clearerr(stdin);
            return 0;
        }
        if(sscanf(first, "%s", rec->name.first) == 1) {
            if(strlen(rec->name.first) < NAMESIZE) {
                break;
            }
        }
        fprintf(stderr, "Incorrect first name.\n");
    }
    while(1) {

        fprintf(stderr, "Enter a valid score between 0 & 100: ");
        if(!fgets(tempScore, BUFSIZE, stdin)) {
            clearerr(stdin);
            return 0;
        }
        if(sscanf(tempScore, "%d", &rec->score) == 1) {
            if (rec->score >= 0 && rec->score <= 100) {
                break;
            }
        }
        fprintf(stderr, "Invalid score.\n");
    }
    return 1;
}

void insert_records(record_list *list) {
    record tempdata;

    if(list->data == 0) {
        list_init(list);
    }
    while(prompt_records(&tempdata)) { 
        list_insert(list, &tempdata);
    } 
}

int cmp_name(const void *p, const void *q) {
    const record *pp = p;
    const record *qq = q;

    int firstName = strcmp(pp->name.first, qq->name.first);
    int lastName = strcmp(pp->name.last, qq->name.last);

    if(lastName == 0) {
        return firstName;
    }
    return lastName;
}

int cmp_ID(const void *p, const void *q) {
    const record *pp = p;
    const record *qq = q;

    return (pp->id - qq->id);

}

void display_ID(record_list *list) {

    size_t i = 0;

    if(list->data == NULL) {
        printf("No records to display.\n");
    }
    qsort(list->data, list->nused, sizeof(record), cmp_ID);

    for(i = 0; i < list->nused; i++){

        printf("%d", list->data[i]->id);
        printf(" %s,", list->data[i]->name.last);
        printf(" %s: ", list->data[i]->name.first);
        printf("%d\n", list->data[i]->score);
    }
}

void display_name(record_list *list) {

    size_t i = 0;
    if(list->data == NULL) {
        printf("No records to display.\n");
    }
    qsort(list->data, list->nused, sizeof(record), cmp_name);

    for(i = 0; i < list->nused; i++){

        if(list->data == NULL) {
            fprintf(stderr, "No records to display.\n");
        }
        printf("%d", list->data[i]->id);
        printf(" %s,", list->data[i]->name.last);
        printf(" %s: ", list->data[i]->name.first);
        printf("%d\n", list->data[i]->score);
    }
} 

Sorry about the messy lines of code but can anyone help me?

I'll start you off. I changed the qsort compare function for IDs and the call parameters to qsort.

The comparison function must return an integer less than, equal to, or greater than zero if the first argument is considered to be respectively less than, equal to, or greater than the second.

EDIT: Not crashing anymore but the sort is not sorting. Still looking.

Edited 4 Years Ago by histrungalot: Fixing

The double pointers threw me at first. You have data which is an array of pointers. When qsort call the compare function the address of the element in the array is passed into it. Thus at that point you have a double pointer again.

Try to fix the other compare function using what I did below.

Code

// ---------------------------------------------
// The compare function must return an integer
// being -1, 0, 1.  The -1 being less than, 0
// is equal and 1 is greater than
// ---------------------------------------------
int cmp_ID(const void *p, const void *q) {
    const record **pp = (const record**)p;   // <-- Had to cast to get rid of warning
    const record **qq = (const record**)q;
    int ret;

    if ( (*pp)->id < (*qq)->id)
      ret = -1;
    else if ( (*pp)->id == (*qq)->id )
      ret = 0;
    else 
      ret = 1;

    return (ret);
}

void display_ID(record_list *list) {  
    size_t i = 0;

    if(list->data == NULL) {
        printf("No records to display.\n");
    }
    //
    // ---------> I changed the third parameter <------------
    //
    // You have an array of pointers to records not records
    //                               |
    //                               V
    qsort(list->data, list->nused, sizeof(record*), cmp_ID);

    for(i = 0; i < list->nused; i++){

        printf("%d", list->data[i]->id);
        printf(" %s,", list->data[i]->name.last);
        printf(" %s: ", list->data[i]->name.first);
        printf("%d\n", list->data[i]->score);
    }
}

Output

Now it's really is sorting!

$ ./a.out
No memory allocated.
------------------
Starting program
------------------

1. Enter records
2. Display records by sorted ID
3. Display records by sorted name
4. Quit
Enter selection: 1
Enter an ID between 1000000 & 9999999: 1000099
Enter a valid last name: Jones
Enter a valid first name: Bill
Enter a valid score between 0 & 100: 88
Enter an ID between 1000000 & 9999999: 1000300
Enter a valid last name: Smith
Enter a valid first name: Bob
Enter a valid score between 0 & 100: 99
Enter an ID between 1000000 & 9999999: 1000001
Enter a valid last name: Kats
Enter a valid first name: Dogs
Enter a valid score between 0 & 100: 55
Enter an ID between 1000000 & 9999999: ^D
1. Enter records
2. Display records by sorted ID
3. Display records by sorted name
4. Quit
Enter selection: 2
1000001 Kats, Dogs: 55
1000099 Jones, Bill: 88
1000300 Smith, Bob: 99

1. Enter records
2. Display records by sorted ID
3. Display records by sorted name
4. Quit
Enter selection: ^C

Great, I managed to fix it using your method and it worked! I still stick with my original code for the cmp functions except with the double pointers and the casting that you put in. When you fixed the ID cmp function, I realized that the name cmp function wasn't working so I applied similar code from the ID cmp and it fixed completely. Everything is fine now.

Thanks histrungalot!

Edited 4 Years Ago by osiron

This question has already been answered. Start a new discussion instead.