简体   繁体   中英

How to fix “Segmentation fault” while printing string of struct?

I wrote a code that stores details of persons in a struct which has a dynamically allocated array of pointers (pointing to structures). These structs are sorted via qsort and should be printed afterwards, but it displays "segmentation fault" right before printing. I assume the problem lies within the printf function and I would be very grateful if someone could explain to me what I have done wrong.

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define MAX 2
#define COUNT_OF(x) (sizeof(x) / sizeof(0[x])) //length of arrays at compile time

struct Person {
char firstname[64];
char lastname[64];
int age;
};

int Person_cmp_firstname(const void* x, const void* y) {
    struct Person *ix = (struct Person *)x;
    struct Person *iy = (struct Person *)y;
    return strcmp(ix->firstname, iy->firstname); 
    }


int Person_cmp_lastname(const void* x, const void* y ) {
    struct Person *ix = (struct Person *)x;
    struct Person *iy = (struct Person *)y;
    return strcmp(ix->lastname, iy->lastname); 
}
int Person_cmp_age(const void* x, const void* y) {
    struct Person *px = (struct Person *) x;
    struct Person *py = (struct Person *) y;
    return px->age - py->age;
}

int main(){;
    int choice;
    struct Person *P[10];
    printf("\t***PROGRAM TO SORT PERSONS***\n\n");
    for(int i=0; i<3; i++){
        P[i] = (struct Person *) malloc(sizeof(struct Person));
        printf("Firstname: ");
        scanf("%s", P[i]->firstname);
        printf("Lastname: ");
        scanf("%s", P[i]->lastname);
        printf("Age: ");
        scanf("%d", &P[i]->age);
        printf("\t***NEXT PERSON***\n\n");
    }
    do{
        printf("\n\t***CHOOSE HOW TO SORT***\n\n\tBy firstname: 1\n\tBy lastname: 2\n\tBy age: 3\n\tExit Program: 4\n\n");
        scanf("%d", &choice);
        switch(choice){
            case 1:
                printf("\t***SORTING BY FIRSTNAME...***\n\n");
                qsort(P, COUNT_OF(P), sizeof(struct Person), Person_cmp_firstname);
                printf("\t***DONE***\n\n");
                for(unsigned int i=0; i<3; i++){
                    printf( "Firstname: %s\t| Lastname: %s\t| Age: %d\n", P[i]->firstname, P[i]->lastname, P[i]->age );
                }
                break;
            case 2:
                printf("\t***SORTING BY LASTNAME...***\n\n");
                qsort(P, COUNT_OF(P), sizeof(struct Person ), Person_cmp_lastname);
                printf("\t***DONE***\n\n");
                for(unsigned int i=0; i<3; i++){
                    printf( "Firstname: %s\t| Lastname: %s\t| Age: %d\n", P[i]->firstname, P[i]->lastname, P[i]->age );
                }
                break;
            case 3:
                printf("\t***SORTING BY AGE...***\n\n");
                qsort(P, COUNT_OF(P), sizeof(struct Person), Person_cmp_age);
                printf("\t***DONE***\n\n");
                for(unsigned int i=0; i<3; i++){
                    printf( "Firstname: %s\t| Lastname: %s\t| Age: %d\n",P[i]->firstname, P[i]->lastname, P[i]->age );
                }
                break;
            case 4:
                printf("\t***EXITING PROGRAM***\n\n");
                for (int j = 0; j < 3; j++) {
                    free(P[j]);
                }
                exit(0);
            default:
                printf("\t***INVALID OPTION***\n\n");
                break;
        }
    }while(1);
    return EXIT_SUCCESS;
}

You have a large number of problems, all stemming from your incorrect handling of your array of pointers :

    struct Person *P[10];

When you declare P above, you have an array of 10 pointers, NOT 10 struct Person . Your allocation, P[i] = (struct Person *) malloc(sizeof(struct Person)); is fine, but See: Do I cast the result of malloc?

You then incorrectly use COUNT_OF(x) to set your loop limits (after hardcoding the 1st to 3 ) which leaves you attempting to qsort and free elements 3-9 that are neither allocated or initialized leading to Undefined Behavior . (your SegFault can occur anywhere)

Your qsort compare functions are wrong. They are off by one-level-of-indirection. You are sorting P which is an array of pointers . Since qsort passes a pointer-to-element to your compare function (remember your elements are pointers not struct), qsort is passing a pointer-to-pointer-to-struct as parameters to your compare function. Therefore your compare functions need to provide an additional level of indirection in the type of variable the const void * parameters are converted to. For example:

int Person_cmp_firstname(const void* x, const void* y) {
    struct Person * const *ix = x;
    struct Person * const * iy = y;
    return strcmp((*ix)->firstname, (*iy)->firstname); 
}


int Person_cmp_lastname(const void* x, const void* y ) {
    struct Person * const *ix = x;
    struct Person * const *iy = y;
    return strcmp((*ix)->lastname, (*iy)->lastname); 
}

int Person_cmp_age(const void* x, const void* y) {
    struct Person * const *px = x;
    struct Person * const *py = y;
    return ((*px)->age > (*py)->age) - ((*px)->age < (*py)->age);
}

( note: since the parameters are void* there is no need to cast the assignments within each compare function. Also note: a subtraction of conditionals is used to avoid potential for overflow, eg (a > b) - (a < b) rather than just a - b for age )

In the remainder of your code, you need a counter to keep track of the number of elements filled, and use that counter as to pass to qsort and to print the sorted arrays (and free the pointers when done). A simple n counter is used below, eg

 int main(){;
    int choice, i, n;
    struct Person *P[10];
    printf("\t***PROGRAM TO SORT PERSONS***\n\n");
    for(i=0; i<3; i++){
        P[i] = (struct Person *) malloc(sizeof(struct Person));
        printf("Firstname: ");
        scanf("%s", P[i]->firstname);
        printf("Lastname: ");
        scanf("%s", P[i]->lastname);
        printf("Age: ");
        scanf("%d", &P[i]->age);
        printf("\t***NEXT PERSON***\n\n");
    }
    n = i;   /* saves the number of filled struct as n */
    ...
                qsort(P, n, sizeof *P, Person_cmp_firstname);
                for(i=0; i<n; i++){
                    printf( "Firstname: %s\t| Lastname: %s\t| Age: %d\n",
                           P[i]->firstname, P[i]->lastname, P[i]->age );
            ...
            case 4:
                printf("\t***EXITING PROGRAM***\n\n");
                for (i = 0; i < n; i++) {
                    free(P[i]);
                }
                exit(0);

Now your code will work.

Putting it altogether, you can do:

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

#define MAX 2
#define COUNT_OF(x) (sizeof(x) / sizeof(*x)) //length of arrays at compile time

struct Person {
    char firstname[64];
    char lastname[64];
    int age;
};

int Person_cmp_firstname(const void* x, const void* y) {
    struct Person * const *ix = x;
    struct Person * const * iy = y;
    return strcmp((*ix)->firstname, (*iy)->firstname); 
}

int Person_cmp_lastname(const void* x, const void* y ) {
    struct Person * const *ix = x;
    struct Person * const *iy = y;
    return strcmp((*ix)->lastname, (*iy)->lastname); 
}

int Person_cmp_age(const void* x, const void* y) {
    struct Person * const *px = x;
    struct Person * const *py = y;
    return ((*px)->age > (*py)->age) - ((*px)->age < (*py)->age);
}

int main(){;
    int choice, i, n;
    struct Person *P[10];
    printf("\t***PROGRAM TO SORT PERSONS***\n\n");
    for(i=0; i<3; i++){
        P[i] = (struct Person *) malloc(sizeof(struct Person));
        printf("Firstname: ");
        scanf("%s", P[i]->firstname);
        printf("Lastname: ");
        scanf("%s", P[i]->lastname);
        printf("Age: ");
        scanf("%d", &P[i]->age);
        printf("\t***NEXT PERSON***\n\n");
    }
    n = i;
    do{
        printf("\n\t***CHOOSE HOW TO SORT***\n\n\tBy firstname: 1\n\tBy lastname: 2\n\tBy age: 3\n\tExit Program: 4\n\n");
        scanf("%d", &choice);
        switch(choice){
            case 1:
                printf("\t***SORTING BY FIRSTNAME...***\n\n");
                qsort(P, n, sizeof *P, Person_cmp_firstname);
                printf("\t***DONE***\n\n");
                for(i=0; i<n; i++){
                    printf( "Firstname: %s\t| Lastname: %s\t| Age: %d\n", P[i]->firstname, P[i]->lastname, P[i]->age );
                }
                break;
            case 2:
                printf("\t***SORTING BY LASTNAME...***\n\n");
                qsort(P, n, sizeof *P, Person_cmp_lastname);
                printf("\t***DONE***\n\n");
                for(i=0; i<n; i++){
                    printf( "Firstname: %s\t| Lastname: %s\t| Age: %d\n", P[i]->firstname, P[i]->lastname, P[i]->age );
                }
                break;
            case 3:
                printf("\t***SORTING BY AGE...***\n\n");
                qsort(P, n, sizeof *P, Person_cmp_age);
                printf("\t***DONE***\n\n");
                for(i=0; i<n; i++){
                    printf( "Firstname: %s\t| Lastname: %s\t| Age: %d\n",P[i]->firstname, P[i]->lastname, P[i]->age );
                }
                break;
            case 4:
                printf("\t***EXITING PROGRAM***\n\n");
                for (i = 0; i < n; i++) {
                    free(P[i]);
                }
                exit(0);
            default:
                printf("\t***INVALID OPTION***\n\n");
                break;
        }
    }while(1);
    return EXIT_SUCCESS;
}

Example Use/Output

$ ./bin/person_struct
        ***PROGRAM TO SORT PERSONS***

Firstname: Porky
Lastname: Pig
Age: 83
        ***NEXT PERSON***

Firstname: Daffy
Lastname: Duck
Age: 93
        ***NEXT PERSON***

Firstname: Mickey
Lastname: Mouse
Age: 100
        ***NEXT PERSON***


        ***CHOOSE HOW TO SORT***

        By firstname: 1
        By lastname: 2
        By age: 3
        Exit Program: 4

2
        ***SORTING BY LASTNAME...***

        ***DONE***

Firstname: Daffy        | Lastname: Duck        | Age: 93
Firstname: Mickey       | Lastname: Mouse       | Age: 100
Firstname: Porky        | Lastname: Pig | Age: 83

        ***CHOOSE HOW TO SORT***

        By firstname: 1
        By lastname: 2
        By age: 3
        Exit Program: 4

3
        ***SORTING BY AGE...***

        ***DONE***

Firstname: Porky        | Lastname: Pig | Age: 83
Firstname: Daffy        | Lastname: Duck        | Age: 93
Firstname: Mickey       | Lastname: Mouse       | Age: 100

        ***CHOOSE HOW TO SORT***

        By firstname: 1
        By lastname: 2
        By age: 3
        Exit Program: 4

4
        ***EXITING PROGRAM***

( note: your input routine is horribly fragile and you fail to check the return of every scanf , further inviting Undefined Behavior with the slip of a single keystroke. Always validate Every user-input and Every allocation)

Memory Use/Error Check

In any code you write that dynamically allocates memory, you have 2 responsibilities regarding any block of memory allocated: (1) always preserve a pointer to the starting address for the block of memory so, (2) it can be freed when it is no longer needed.

It is imperative that you use a memory error checking program to insure you do not attempt to access memory or write beyond/outside the bounds of your allocated block, attempt to read or base a conditional jump on an uninitialized value, and finally, to confirm that you free all the memory you have allocated.

For Linux valgrind is the normal choice. There are similar memory checkers for every platform. They are all simple to use, just run your program through it.

$ valgrind ./bin/person_struct
==2078== Memcheck, a memory error detector
==2078== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==2078== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==2078== Command: ./bin/person_struct
==2078==
        ***PROGRAM TO SORT PERSONS***

Firstname: John
Lastname: Wayne
Age: 91
        ***NEXT PERSON***

Firstname: Jane
Lastname: Doe
Age: 101
        ***NEXT PERSON***

Firstname: Mickey
Lastname: Mouse
Age: 99
        ***NEXT PERSON***


        ***CHOOSE HOW TO SORT***

        By firstname: 1
        By lastname: 2
        By age: 3
        Exit Program: 4

1
        ***SORTING BY FIRSTNAME...***

        ***DONE***

Firstname: Jane | Lastname: Doe | Age: 101
Firstname: John | Lastname: Wayne       | Age: 91
Firstname: Mickey       | Lastname: Mouse       | Age: 99

        ***CHOOSE HOW TO SORT***

        By firstname: 1
        By lastname: 2
        By age: 3
        Exit Program: 4

2
        ***SORTING BY LASTNAME...***

        ***DONE***

Firstname: Jane | Lastname: Doe | Age: 101
Firstname: Mickey       | Lastname: Mouse       | Age: 99
Firstname: John | Lastname: Wayne       | Age: 91

        ***CHOOSE HOW TO SORT***

        By firstname: 1
        By lastname: 2
        By age: 3
        Exit Program: 4

3
        ***SORTING BY AGE...***

        ***DONE***

Firstname: John | Lastname: Wayne       | Age: 91
Firstname: Mickey       | Lastname: Mouse       | Age: 99
Firstname: Jane | Lastname: Doe | Age: 101

        ***CHOOSE HOW TO SORT***

        By firstname: 1
        By lastname: 2
        By age: 3
        Exit Program: 4

4
        ***EXITING PROGRAM***

==2078==
==2078== HEAP SUMMARY:
==2078==     in use at exit: 0 bytes in 0 blocks
==2078==   total heap usage: 3 allocs, 3 frees, 396 bytes allocated
==2078==
==2078== All heap blocks were freed -- no leaks are possible
==2078==
==2078== For counts of detected and suppressed errors, rerun with: -v
==2078== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

Always confirm that you have freed all memory you have allocated and that there are no memory errors.

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