简体   繁体   中英

why when i put a proper value in semester variable my program crashes? C

struct student{
    char *number;
    char *name;
    int semester;
    float *grades;
};

typedef struct student RECORD;

void read_record(RECORD *p);
void print_record(RECORD x);
void init_record(RECORD *p);
void free_record(RECORD x);

main()
{
    int N;
    printf("Please enter the number of students that you want to insert: ");
    scanf("%d",&N);

    RECORD array[N];
    int i;

    for (i=0; i<N; i++)
        init_record(&array[i]);

at this point, when i put the semester value from the keyboard my program crashes

for (i=0; i<N; i++)
{
    printf("Number %d student: \n",i+1);
    read_record(&array[i]);     
}


for (i=0; i<N; i++)
print_record(array[i]);


    for (i=0; i<N; i++)
    {
        free_record(array[i]);
    }   
}

I tried to remove any unnecessary code but I left the most important parts so any false part would be easier to be seen

void read_record(RECORD *p)
{
        int i;

    printf("Please give the name: ");
    scanf("%s", p->name);

    printf("Please give AM number: ");
    scanf("%s", p->number);

    printf("Please give semester: ");
    scanf("%d",p->semester);

    printf("Please give grades: ");
    for(i=0;i<5;i++)
    scanf("%f", &(p->grades[i]));
}

After this piece of code I have the init_record function that includes mallocs for struct's arrays

void init_record(RECORD *p)
{

    p->name = malloc(sizeof(char)*40);
    if (!p->name)
    {
        printf("Error!");
        exit(0);
    }

    p->number = malloc(sizeof(char)*6);
    if (!p->number)
    {
        printf("Error!");
        exit(0);
    }

    p->grades = malloc(sizeof(float)*5);
    if (!p->grades)
    {
        printf("Error!");
        exit(0);
    }
}

Assuming that you've allocated the memory for the struct members

printf("Please give semester: ");
scanf("%d",p->semester);

is the problem, as p->semester is an int . %d expects a pointer to int , you are passing an uninitialized integer value as a pointer to scanf .

The correct call would be

scanf("%d", &p->semester);

Usually once uses malloc because one need objects that live outside the scope of function, for example with linked lists, trees, etc. But also when you need an array whose dimension is not known on compiler time.

Your init_record seems to me pointless, because you know the size of the arrays and they are even small, you could easily change your struct to

struct student {
    char number[6];
    char name[40];
    int semester;
    float grades[5];
};

and then you wouldn't need to do the malloc calls.

Also, you would need to do the scanf like this:

int c;

scanf("%39s", p->name);
while((c = getchar()) != '\n' && c != EOF); // clearing the buffer

scanf("%5s", p->number);
while((c = getchar()) != '\n' && c != EOF); // clearing the buffer

...

to prevent buffer overflows if the user enters a name/number that are too large for the buffer.


edit

OP asked in the comments

One last thing: I wanted to replace scanf with gets in the case of name, because I want a name and a surname to be saved as one piece(including space). But when I replaced scanf, I got this error: request for member 'name' in something not a structure or union. I searched over here for a similar problem but I didn't find any solution. Can you suggest one?

Don't use gets , this is a dangerous function because it doesn't take the size of the array into account and if the entered text is larger than the buffer size, it will overflow and cause a lot of damage. The error message is due to a syntax error, as you don't show the code, I cannot say what you did wrong. However, if you take my advice, you won't have this error.

In general I advice not to use scanf to read from the user, because scanf was not designed to do so. Specially when you want to read strings that have empty spaces, it's better to read the whole line with fgets and parse the line later using other functions like strchr , strstr , strtok , sscanf , strtol , etc. Which function to use depends on what you are trying to read from the user. In this case where you are reading strings, fgets would give better results. So I'd change your whole reading process to this:

int read_record(RECORD *p)
{
    char line[1024];

    printf("Please give the name: ");
    if(fgets(line, sizeof line, stdin) == NULL)
    {
        fprintf(stderr, "Could not read name\n");
        return 0; // error
    }

    line[strcspn(line, "\n")] = 0; // removing newline
    strncpy(p->name, line, sizeof p->name);
    p->name[sizeof(p->name) - 1] = 0; // making sure to get a valid string

    printf("Please give AM number: ");
    if(fgets(line, sizeof line, stdin) == NULL)
    {
        fprintf(stderr, "Could not read AM number\n");
        return 0;
    }

    line[strcspn(line, "\n")] = 0; // removing newline
    strncpy(p->number, line, sizeof p->name);
    p->name[sizeof(p->number) - 1] = 0; // making sure to get a valid string

    // this is ok, this can stay like this
    printf("Please give semester: ");
    scanf("%d", &p->semester);

    printf("Please give grades: ");
    if(fgets(line, sizeof line, stdin) == NULL)
    {
        fprintf(stderr, "Could not read grades\n");
        return 0;
    }

    if(sscanf(line, "%f %f %f %f %f",
                p->grades, p->grades + 1, p->grades + 2, p->grades + 3,
                p->grades + 4) != 5)
    {
        fprintf(stderr, "Could not read 5 grades\n");
        return 0;
    }

    return 1; // success
}

I know that it is much more code that you have before, but this code is more robust, it handles cases where the user did input the wrong format, your code can react to that, print error messages, retry the user input, whatever. With your the error manifest themselves when the data printed on screen or a file is strange.

Note how I copied the string:

strncpy(p->name, line, sizeof p->name);
p->name[sizeof(p->name) - 1] = 0; // making sure to get a valid string

Here I assuming that you've changed the struct to hold arrays like I said in the first part of the answer. If you didn't change that and you are still using the old way by malloc ing with a hardcoded fixed size, you will have to use the hardcoded fixed size here as well:

strncpy(p->name, line, 40);
p->name[39] = 0; // making sure to get a valid string

And you see why I prefer when the struct has the array, because with sizeof I can get the size regardless of the dimension.

The other thing to notice here is that I used strncpy instead of strcpy . strcpy suffers from the same problems as gets , it doesn't take the size of the destination buffer into consideration and if the source string is larger than the destination buffer, it will overflow the buffer.

strncpy works like strcpy except that you pass how many bytes there is available for the destination buffer. If the source is larger than that number, then strncpy will not writing bytes in the destination, thus preventing a buffer overflow. Of course if the '\\0' -terminating byte is not among the copied bytes, it won't be written in the destination buffer. p->name[39] = 0; just makes sure that the string is '\\0' -terminated, no matter how long the source was.

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