简体   繁体   中英

Problem with reading a file by using FREAD into struct in C

I wrote the code below to read a file into a structure but the output when I run this code can be seen in the text below. I want to read the file into the structure with its each char block like number, name, surname.. Original file is also like below.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
struct StudentInfo{
    char studentNumber[20];
    char studentName[20];
    char studentSurname[20];
    char department[20];
    char eMail[20];
};
int main(int argc, char *argv[]) {
    
    //Declarations
    char firstFileName[20];
    char secondFileName[20];
    char operationType[2];
    char data[100];
    struct StudentInfo *studentInfo=(struct StudentInfo*) malloc(sizeof(struct StudentInfo));
    FILE *firstFile;
    FILE *secondFile;
    printf("Enter the first file name: ");
    scanf("%s",firstFileName);
    printf("Enter the second file name: ");
    scanf("%s",secondFileName);
    printf("Enter the operation type (-i, -u):");
    scanf("%s",operationType);
    
    if(strcmp(operationType,"-i")==0){
    
        firstFile = fopen(strcat(firstFileName,".txt"),"r");
        while(!feof(firstFile)){
            fread(studentInfo,sizeof(struct StudentInfo),1,firstFile);
            printf("%s",studentInfo->studentNumber);
        }
    }
    if(strcmp(operationType,"-u")==0){
        printf("%s",secondFileName);    
    }
    fclose(firstFile);

    /*
    while(!feof(firstFile)){
        students[1]->studentNumber = 
    }
    fclose(firstFile);*/
    
}

ORIGINAL CONTENT OF THE FILE IS:

Ogrenci No;Ogrenci;Program;Sinif;Email;Status
10000000000;EDA NUR   YILMAZ;Computer Engineering;4;enur.yilmaz@tedu.edu.tr;
10000000010;FEYZA  NUR  DUMAN;Computer Engineering;2;fnur.duman@tedu.edu.tr;
20000000010;GOKHAN  YAMAC;Computer Engineering;2;gokhan.yamac@tedu.edu.tr;
30000000030;CEREN  AYDINLI;Computer Engineering;2;ceren.aydinli@tedu.edu.tr;
30000000010;DURU  YAMAC;Computer Engineering;3;duru.yamac@tedu.edu.tr;
40000000010;SEVIL TERZI;Computer Engineering;2;sevil.terzi@tedu.edu.tr;
50000000010;EREN  AYDIN;Computer Engineering;2;eren.aydin@tedu.edu.tr;
50000000020;YAMAC    YILMAZ;Computer Engineering;2;yamac.yilmaz@tedu.edu.tr;
60000000020;EDANUR    YILMAZ;Computer Engineering;2;edanur.yilmaz@tedu.edu.tr;
70000000010;GOKHAN YAMAC;Computer Engineering;2;gokhan.yamac18@tedu.edu.tr;

OUTPUT OF MY CODE IS:

Ogrenci No;Ogrenci;Program;Sinif;Email;Status
10000000000;EDA NUR   YILMAZ;Computer Engineering;4;enPI_K³Æ_aj©ur.yilmaz@tedu.edu.tr;
10000000010;FEYZA  NUR  DUMAN;Computer Engineering;2;fnur.duman@tedu.edu.tr;
PI_K³Æ_aj©20000000010;GOKHAN  YAMAC;Computer Engineering;2;gokhan.yamac@tedu.edu.tr;
30000000030;CEREN  AYDINLPI_K³Æ_aj©I;Computer Engineering;2;ceren.aydinli@tedu.edu.tr;
30000000010;DURU  YAMAC;Computer Engineering;3;dPI_K³Æ_aj©uru.yamac@tedu.edu.tr;
40000000010;SEVIL TERZI;Computer Engineering;2;sevil.terzi@tedu.edu.tr;
50000PI_K³Æ_aj©000010;EREN  AYDIN;Computer Engineering;2;eren.aydin@tedu.edu.tr;
50000000020;YAMAC    YILMAZ;ComputPI_K³Æ_aj©er Engineering;2;yamac.yilmaz@tedu.edu.tr;
60000000020;EDANUR    YILMAZ;Computer Engineering;2;edanuPI_K³Æ_aj©r.yilmaz@tedu.edu.tr;
70000000010;GOKHAN YAMAC;Computer Engineering;2;gokhan.yamac18@tedu.edu.tr;
nuPI_K³Æ_aj©

The loop

while(!feof(firstFile)){
    fread(studentInfo,sizeof(struct StudentInfo),1,firstFile);
    printf("%s",studentInfo->studentNumber);
}

is wrong, for several reasons:

The loop condition is wrong. See the following question for further information: Why is “while ( !feof (file) )” always wrong?

The line

fread(studentInfo,sizeof(struct StudentInfo),1,firstFile);

will attempt to read exactly sizeof(struct StudentInfo) bytes, which is 100 bytes, from the file. In other words, your program is assuming that every line is exactly 100 bytes long, and that the next entry is also exactly 100 bytes long. Your program is also assuming that there is nothing in between these 100 byte long entries (so also no newline character). Additionally, your program is assuming that every string in the file has a null terminating character in it (because you are later attempting to print them as null-terminated strings). All three of these assumptions are wrong.

What you actually have is a file in which each entry is separated by a newline character. The fields of the entries are of variable length (not a fixed length of 20), which are separated by ; characters. Therefore, it would be more appropriate to read one line at a time using the function fgets , and to use strtok to divide the line into its individual fields:

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

struct StudentInfo
{
    char studentNumber[20];
    char studentName[20];
    char department[20];
    char unknown[20];
    char eMail[20];
};

int main( void )
{
    FILE *fpInput;
    char line[400];
    struct StudentInfo si;

    //open input file
    fpInput = fopen( "input.txt", "r" );
    if ( fpInput == NULL )
    {
        fprintf( stderr, "Error opening file!\n" );
        exit( EXIT_FAILURE );
    }

    //ignore first line of input
    fgets( line, sizeof line, fpInput );

    //read one line of input per loop iteration
    while ( fgets( line, sizeof line, fpInput ) != NULL )
    {
        char *p;

        //attempt to find newline character
        p = strchr( line, '\n' );

        //make sure entire line was read, and remove newline
        //character if necessary
        if ( p == NULL )
        {
            //a missing newline character is ok on end-of-file
            if ( !feof(fpInput) )
            {
                fprintf( stderr, "Line too long for input buffer!\n" );
                exit( EXIT_FAILURE );
            }
        }
        else
        {
            //remove newline character from input
            *p = '\0';
        }

        //attempt to read student number
        p = strtok( line, ";" );
        if ( p == NULL )
        {
            fprintf( stderr, "Unable to find student number!\n" );
            exit( EXIT_FAILURE );
        }

        //attempt to copy student number to struct
        if ( snprintf( si.studentNumber, sizeof si.studentNumber, "%s", p ) >= (int)(sizeof si.studentNumber) )
        {
            fprintf( stderr, "Not enough space to copy student number.\n" );
            exit( EXIT_FAILURE );
        }

        //attempt to read student name
        p = strtok( NULL, ";" );
        if ( p == NULL )
        {
            fprintf( stderr, "Unable to find student name!\n" );
            exit( EXIT_FAILURE );
        }

        //attempt to copy student name to struct
        if ( snprintf( si.studentName, sizeof si.studentName, "%s", p ) >= (int)(sizeof si.studentName) )
        {
            fprintf( stderr, "Not enough space to copy student name.\n" );
            exit( EXIT_FAILURE );
        }

        //attempt to read department
        p = strtok( NULL, ";" );
        if ( p == NULL )
        {
            fprintf( stderr, "Unable to find department!\n" );
            exit( EXIT_FAILURE );
        }

        //attempt to copy department to struct
        if ( snprintf( si.department, sizeof si.department, "%s", p ) >= (int)(sizeof si.department) )
        {
            fprintf( stderr, "Not enough space to copy department.\n" );
            exit( EXIT_FAILURE );
        }

        //attempt to read unknown field
        p = strtok( NULL, ";" );
        if ( p == NULL )
        {
            fprintf( stderr, "Unable to find unknown field!\n" );
            exit( EXIT_FAILURE );
        }

        //attempt to copy unknown field to struct
        if ( snprintf( si.unknown, sizeof si.unknown, "%s", p ) >= (int)(sizeof si.unknown) )
        {
            fprintf( stderr, "Not enough space to copy unknown field.\n" );
            exit( EXIT_FAILURE );
        }

        //attempt to read email
        p = strtok( NULL, ";" );
        if ( p == NULL )
        {
            fprintf( stderr, "Unable to find email!\n" );
            exit( EXIT_FAILURE );
        }

        //attempt to copy email to struct
        if ( snprintf( si.eMail, sizeof si.eMail, "%s", p ) >= (int)(sizeof si.eMail) )
        {
            fprintf( stderr, "Not enough space to copy eMail.\n" );
            exit( EXIT_FAILURE );
        }

        //print data from struct
        printf(
            "Successfully read the following student entry:\n"
            "Number    : %s\n"
            "Name      : %s\n"
            "Department: %s\n"
            "Unknown   : %s\n"
            "E-Mail    : %s\n"
            "\n",
            si.studentNumber, si.studentName, si.department,
            si.unknown, si.eMail
        );
    }
}

Since you have not yet responded to my request for clarification on what the meaning of the fourth field in the input field is, I am simply labelling it as "unknown" in stuct StudentInfo .

However, when I run this program, I get the following error message:

Not enough space to copy department.

This is because your definition of struct StudentInfo contains the following line:

char department[20];

This means that it is only able to store 19 characters plus the terminating null character. However, in the input file, you have Computer Engineering , which is 20 characters long ( 21 including the terminating null character). Therefore, you must increase the size of the array to at least 21 .

You have the same problem with

char eMail[20];

This array is not large enough to store the string

enur.yilmaz@tedu.edu.tr

in the input file, because it requires 24 characters (including the terminating character)

After increasing the size of both arrays from 20 to 30 , the program will work. It will have the following output:

Successfully read the following student entry:
Number    : 10000000000
Name      : EDA NUR   YILMAZ
Department: Computer Engineering
Unknown   : 4
E-Mail    : enur.yilmaz@tedu.edu.tr

Successfully read the following student entry:
Number    : 10000000010
Name      : FEYZA  NUR  DUMAN
Department: Computer Engineering
Unknown   : 2
E-Mail    : fnur.duman@tedu.edu.tr

Successfully read the following student entry:
Number    : 20000000010
Name      : GOKHAN  YAMAC
Department: Computer Engineering
Unknown   : 2
E-Mail    : gokhan.yamac@tedu.edu.tr

Successfully read the following student entry:
Number    : 30000000030
Name      : CEREN  AYDINLI
Department: Computer Engineering
Unknown   : 2
E-Mail    : ceren.aydinli@tedu.edu.tr

Successfully read the following student entry:
Number    : 30000000010
Name      : DURU  YAMAC
Department: Computer Engineering
Unknown   : 3
E-Mail    : duru.yamac@tedu.edu.tr

Successfully read the following student entry:
Number    : 40000000010
Name      : SEVIL TERZI
Department: Computer Engineering
Unknown   : 2
E-Mail    : sevil.terzi@tedu.edu.tr

Successfully read the following student entry:
Number    : 50000000010
Name      : EREN  AYDIN
Department: Computer Engineering
Unknown   : 2
E-Mail    : eren.aydin@tedu.edu.tr

Successfully read the following student entry:
Number    : 50000000020
Name      : YAMAC    YILMAZ
Department: Computer Engineering
Unknown   : 2
E-Mail    : yamac.yilmaz@tedu.edu.tr

Successfully read the following student entry:
Number    : 60000000020
Name      : EDANUR    YILMAZ
Department: Computer Engineering
Unknown   : 2
E-Mail    : edanur.yilmaz@tedu.edu.tr

Successfully read the following student entry:
Number    : 70000000010
Name      : GOKHAN YAMAC
Department: Computer Engineering
Unknown   : 2
E-Mail    : gokhan.yamac18@tedu.edu.tr

However, one thing that is not so nice about this solution is that it contains a significant amount of code duplication. The code for handling all 5 fields is nearly identical, so it would be better to unify this code:

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

struct StudentInfo
{
    char studentNumber[20];
    char studentName[20];
    char department[30];
    char unknown[20];
    char eMail[30];
};

int main( void )
{
    FILE *fpInput;
    char line[400];
    struct StudentInfo si;

    struct field
    {
        char *p_str;
        size_t size;
    };

    //arrange fields in an array, so that it can be used in
    //a loop
    struct field fields[] = {
        { si.studentNumber, sizeof si.studentNumber },
        { si.studentName,   sizeof si.studentName },
        { si.department,    sizeof si.department },
        { si.unknown,       sizeof si.unknown },
        { si.eMail,         sizeof si.eMail }
    };

    //open input file
    fpInput = fopen( "input.txt", "r" );
    if ( fpInput == NULL )
    {
        fprintf( stderr, "Error opening file!\n" );
        exit( EXIT_FAILURE );
    }

    //ignore first line of input
    fgets( line, sizeof line, fpInput );

    //read one line of input per loop iteration
    while ( fgets( line, sizeof line, fpInput ) != NULL )
    {
        char *p;

        //attempt to find newline character
        p = strchr( line, '\n' );

        //make sure entire line was read, and remove newline
        //character if necessary
        if ( p == NULL )
        {
            //a missing newline character is ok on end-of-file
            if ( !feof(fpInput) )
            {
                fprintf( stderr, "Line too long for input buffer!\n" );
                exit( EXIT_FAILURE );
            }
        }
        else
        {
            //remove newline character from input
            *p = '\0';
        }

        p = strtok( line, ";" );

        for ( size_t i = 0; i < sizeof fields / sizeof *fields; i++ )
        {
            //verify that field exists
            if ( p == NULL )
            {
                fprintf( stderr, "Unable to find student number!\n" );
                exit( EXIT_FAILURE );
            }

            //attempt to copy field to struct
            if ( snprintf( fields[i].p_str, fields[i].size, "%s", p ) >= (int)(fields[i].size) )
            {
                fprintf( stderr, "Not enough space to copy field.\n" );
                exit( EXIT_FAILURE );
            }

            p = strtok( NULL, ";" );
        }

        //print data from struct
        printf(
            "Successfully read the following student entry:\n"
            "Number    : %s\n"
            "Name      : %s\n"
            "Department: %s\n"
            "Unknown   : %s\n"
            "E-Mail    : %s\n"
            "\n",
            si.studentNumber, si.studentName, si.department,
            si.unknown, si.eMail
        );
    }
}

Your code presents many problems:

  • char operationType[2]; can only hold one character (the other one is for \0 );
  • struct StudentInfo *studentInfo=(struct StudentInfo*) malloc(sizeof(struct StudentInfo)); is too unnecessarily long;
  • while(!feof(firstFile)) is always wrong ;
  • You are misusing scanf ;
  • You are not checking the return values of fopen and fread ;
  • The format of your file suggests it is a csv with a header. In your case, fread is not the proper way to read your file into struct StudentInfo . fgets is more appropriate.

@Andreas Wenzel already provided a solution . The following is another version using sscanf :

First, redefine your struct so it makes more sense:

struct StudentInfo {
    char studentNumber[12];
    char studentName[50];
    char department[50];
    int weired;  // What is this?
    char eMail[50];
};

Second, define a function that reads one line from an input stream:

char *read_line(char *line, size_t size, FILE *stream)
{
    if(!fgets(line, size, stream))
        return NULL;
    
    size_t npos = strcspn(line, "\n");
    line[npos] = '\0';
    return line;
}

Then use it in combination of sscanf :

int main(int argc, const char *argv[])
{
    const char *filename = "file.txt";
    
    FILE *file = fopen(filename, "r");
    if (!file) {
        fprintf(stderr, "Could not open file %s\n", filename);
        exit(EXIT_FAILURE);
    }
    
    char line[1024]; // 1KB should be large enough
    struct StudentInfo students[100];
    size_t nstudents = 0;
    
    // Read and discard the first line
    read_line(line, sizeof line, file);
    
    // Read the rest of the file
    for (size_t lineno = 0; read_line(line, sizeof line, file); ++lineno) {
        char studentNumber[12];
        char studentName[50];
        char department[50];
        int weired;
        char eMail[50];
        
        int ret = sscanf(line, " %11[^;\n];%49[^;\n];%49[^;\n];%d;%49[^;\n];", studentNumber, studentName, department, &weired, eMail);
        
        if (ret != 5) {
            printf("> [ERROR] on line %ld: expected %d fields, but only parsed %d\n", lineno, 5, ret);
            continue;
        }
        
        // Copy data to struct array only when parsing succeeded
        
        struct StudentInfo *st = &students[nstudents++];
        strncpy(st->studentNumber, studentNumber, sizeof(studentNumber));
        strncpy(st->studentName, studentName, sizeof(studentName));
        strncpy(st->department, department, sizeof(department));
        st->weired = weired;
        strncpy(st->eMail, eMail, sizeof(eMail));
        
        //printf("%s\n%s\n%s\n%d\n%s\n\n", studentNumber, studentName, department, weired, eMail);
    }
    
    fclose(file);
    
    for (size_t i = 0; i < nstudents; ++i) {
        struct StudentInfo *st = &students[i];
        printf("Student #%ld\n", i+1);
        printf("%s\n%s\n%s\n%d\n%s\n\n", st->studentNumber, st->studentName, st->department, st->weired, st->eMail);
    }
}

Here, the %11[^;\n]; part means "read everything until a newline or a semicolon is found, or the maximum limit of 11 characters is hit. Then read a semicolon."

Output:

Student #1
10000000000
EDA NUR   YILMAZ
Computer Engineering
4
enur.yilmaz@tedu.edu.tr

Student #2
10000000010
FEYZA  NUR  DUMAN
Computer Engineering
2
fnur.duman@tedu.edu.tr

Student #3
20000000010
GOKHAN  YAMAC
Computer Engineering
2
gokhan.yamac@tedu.edu.tr

Student #4
30000000030
CEREN  AYDINLI
Computer Engineering
2
ceren.aydinli@tedu.edu.tr

Student #5
30000000010
DURU  YAMAC
Computer Engineering
3
duru.yamac@tedu.edu.tr

Student #6
40000000010
SEVIL TERZI
Computer Engineering
2
sevil.terzi@tedu.edu.tr

Student #7
50000000010
EREN  AYDIN
Computer Engineering
2
eren.aydin@tedu.edu.tr

Student #8
50000000020
YAMAC    YILMAZ
Computer Engineering
2
yamac.yilmaz@tedu.edu.tr

Student #9
60000000020
EDANUR    YILMAZ
Computer Engineering
2
edanur.yilmaz@tedu.edu.tr

Student #10
70000000010
GOKHAN YAMAC
Computer Engineering
2
gokhan.yamac18@tedu.edu.tr

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