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?

Dani AI

Generated

The segfault was caused by a mismatch between the array element type and how qsort and the compare functions were written. The program stores record * values in an array, so qsort sees elements that are pointers. Treating those elements as record objects (and passing sizeof(record) to qsort) makes qsort and the comparator read the wrong memory and leads to invalid reads/corruption. correctly spotted that the compare function must deal with the double-pointer form and that the element size must match the array element type.

Fixes that address the root cause:

  • Call qsort with the size of an array element, not the size of the pointee. A robust expression is sizeof list->data[0] (this evaluates to the pointer size for an array of pointers).
  • Implement each comparator so it casts the const void * params to a pointer-to-element-type (for this program, to const record *const *), then dereference once to get the actual record * values before comparing fields. Prefer explicit comparisons that return negative/zero/positive (e.g., compare ids with </>), rather than relying on integer subtraction which can overflow in other contexts.

Other cleanups and hardening:

  • The initial if (list->data == NULL) right after malloc(sizeof(record_list)) is invalid because list->data is uninitialized; check the result of malloc itself (if (list == NULL)) and call list_init afterward.
  • Check all malloc/realloc return values, free the record_list container itself (not just its contents), and consider running the program under a memory checker (valgrind or ASan) to catch any remaining invalid reads/writes.

Summary: the double-pointer/qsort-size mismatch produced the crash; the change described by (casting compare args to pointer-to-pointer and using the pointer-size for qsort) is the correct fix and confirms it resolves the segfault.

Recommended Answers

All 3 Replies

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

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.

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!

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.