2

So I'm working on a program in C where I take input from a file and put it in an array of Structs ... however never having gotten input from a file in C before, I'm a bit confused. I've tried multiple ways of doing it, and all of them came with a pretty similar result: The first variable is fine, the second variable is fine (except only half of the name is copied), the third and fourth variable are random gibberish. How big the gibberish is depends on how I write the program, but it is always random characters.

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

struct student {
    int student_ID;
    char name[26];
    char degree[26];
    char campus[26];

};

void main() {
    FILE* fileToOpen = fopen("student database.txt", "r");

    if (fileToOpen == NULL) {
        printf("File cannot be opened!...\n...");
        exit(0);
    }

    struct student studentList[12];

    char buffer[200];
    fgets(buffer, 200, fileToOpen); 

    int counter = 0;
    int value; 
    while (!feof(fileToOpen)) {
        value = sscanf(buffer, "%d,%s,%s,%s", &studentList[counter].student_ID, &studentList[counter].name, &studentList[counter].degree, &studentList[counter].campus);
        printf("Read point: %d %s\n", studentList[counter].student_ID, studentList[counter].name);
        fgets(buffer, 200, fileToOpen);
        counter++;
    }
    printf("\n\n\n");
    printf("First point: % d % s % s % s\n", studentList[0].student_ID, studentList[0].name, studentList[0].degree, studentList[0].campus);
    printf("Second point: % d % s % s % s\n", studentList[1].student_ID, studentList[1].name, studentList[1].degree, studentList[1].campus);
    printf("\nvalue: %d", value);
    fclose(fileToOpen);

}

I don't get what's going on here. I've tried other input methods (ones that iddn't involve that char buffer array, fgets, etc), followed guides step by step, yet the print statements always revealed that only part of the data is input correctly is the first variable, and half of the second variable (first name). You can see that in the final two print statements. What's the problem?

Here is the text file for reference:

895329,Tom Elder,Computer Science,Downtown Campus
564123,Elissa Honk,Interior Design,East Campus
963474,Alfonso Dobra,Civil Engineering,West Campus
127485,Paolo Morisa,Accounting,West Campus
330021,Lisa Bali,Accounting,Downtown Campus
844112,Eli Dovian,Computer Science,East Campus
745112,Rola Etrania,Civil Engineering,East Campus
541247,Pamela Dotti,Interior Design,East Campus
745930,Paul Sabrini,Accounting,Downtown Campus
500124,Gabriella Alma,Accounting,Downtown Campus
741206,Joe Damian,Computer Science,West Campus
963100,Perla Kino,Interior Design,East Campus

Thanks for any help and advice. This is all the program is so far.

student
  • 21
  • 2
  • 1
    Fyi, `%s` will consume commas too. So long as there is no whitespace to intercede and stop the consumption, it will basically eat *anything* (unbounded btw, hint). I suspect `strtok` and `strcpy` (more or less) may be on the menu in the not-too-distance future. – WhozCraig Apr 17 '22 at 17:59
  • 1
    studnet, What text or who suggested `while (!feof(fileToOpen)) {`? – chux - Reinstate Monica Apr 17 '22 at 18:03
  • the !feof thing is a result of the last rewritten main I tried, this is the latest version of the program, i've rewritten this input many times. It was this youtube video: https://youtu.be/shYMgRcjm5A – student Apr 17 '22 at 18:05
  • It's one of the rare correct usages of `feof()` seen here. It's not idiomatic though: `while(fgets(buffer, 200, fileToOpen) != NULL)` is more usual. – Weather Vane Apr 17 '22 at 18:07
  • 1
    @student props on being able to recite where that came from. It isn't contributing to your actual problem, but at least you could answer the question (most people just echo crickets). Anyway, your usage isn't the normal utterly-wrong implementation normally seen here. It can work, but you can avoid the unhinged criticism by getting rid of the `fgets` call before your loop, *and* the one *in* your loop, and just using `while (fgets(buffer, 200, fileToOpen) != NULL)` as your loop condition. It also fixes the unusual circumstance of a stream-error-only (so ferror is true, but feof is not). – WhozCraig Apr 17 '22 at 18:10
  • 2
    Please be aware that `%s` is useless for reading the multi-word input that you need: it stops at the first whitespace character. – Weather Vane Apr 17 '22 at 18:11
  • @student Please let me know if my answer is useful or not! – OTheDev Apr 19 '22 at 08:44

2 Answers2

0
  • Test the return value of fgets(), not feof(). Why is “while ( !feof (file) )” always wrong?. Avoid magic numbers like 200. Use the size of buffer.

    // fgets(buffer, 200, fileToOpen); 
    // while (!feof(fileToOpen)) {
      ...
      // fgets(buffer, 200, fileToOpen);
    
    while (fgets(buffer, sizeof buffer, fileToOpen)) {
    
  • Do not try to use more than 12 studentList[counter].

Test counter < 12 someplace.

  • To scan characters except for a ',', do not use "%s", use " %[^,]". Use a width to prevent buffer overrun. "%s" skips leading white-space and then scans in all non-white-space. A ',' is not special.

    // sscanf(buffer, "%d,%s,%s,%s"
    sscanf(buffer, "%d, %19[^,], %19[^,], %19[^,\n]"
    
  • Check the return value from sscanf(). Do not use studentList[counter] unless scan was successful.

    value = sscanf(buffer, "%d, %19[^,], %19[^,], %19[^,\n]" ...
    if (value != 4) {
      puts("Failed to scan <%s>\n", buffer);    
    } else {
      ; // use studentList[counter]
    }
    
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
0

Here is a way to do what you want without the use of a char array as a buffer or some kind of scanf. The contents of "blankpaper.txt" are the same as that provided at the end of your post. getc() is used to read each character from the file.

The digits at the beginning of a line and before the first comma are stored in a digits array of characters. The length of the digits array is set to one plus the maximum number of digits that an int requires (calculated using the num_digits function defined after main). The digits string is null-terminated. Using strtol, we convert digits to a long. We check to see strtol consumed the entire string, that the integer the string represents fits inside the range of long values, and that the returned integer can fit inside an int (which is the type for the id member of a student structure).

For those members of the student structure that refer to char arrays, we make sure that we do not attempt to store more than NAME_LEN (a macro representing 25) characters. These arrays have length NAME_LEN + 1 so that they can be properly null-terminated. Finding a comma signals we need to start storing characters in another char array. Let me know if there are any questions!

Program

#include <stdio.h> 
#include <stdlib.h>
#include <errno.h>
#include <limits.h>

#define NUM_STUD 12    /* number of students */
#define NAME_LEN 25    /* max 'name' length for name, degree, or campus */
#define FILE_NAME "blankpaper.txt"

struct student {
    int id;
    char name[NAME_LEN + 1];
    char degree[NAME_LEN + 1];
    char campus[NAME_LEN + 1];
};

int num_digits(long n);

int main(void) {
    struct student a[NUM_STUD];            /* array of student structs */


    int max_digits = num_digits(INT_MIN);  /* max number of decimal digits in
                                              an int */
    char digits[max_digits + 1];           /* digits of id of student */
    char *ptr;                             /* used in second argument of
                                              strtol for str-num conversion */
    long r;                                /* used for return value of strtol
                                              */


    size_t i, j, k;                        /* loop variables */
    char *b[3];                            /* pointers to 'name' strings */
    int ch;                                /* reads characters */


    FILE *fp = fopen(FILE_NAME, "r");      /* open file for reading */

    /* if cannot open file */
    if (fp == NULL) {
        printf("Cannot open %s\n", FILE_NAME);
        exit(EXIT_FAILURE);
    }

    for (k = 0; k < NUM_STUD; k++) {

        /* read and store the integer, also read comma */
        i = 0;
        while ((ch = getc(fp)) != '\n' && ch != ',' && ch != EOF)
            if (i < max_digits)
                digits[i++] = ch;
        digits[i] = '\0';    /* null-terminate string */

        /* errno is from <errno.h>.
           value of the macro ERANGE is stored in errno if correct value
           outside range of representable values (i.e. conversion produces
           value outside range of strtol's return type) */
        errno = 0;

        r = strtol(digits, &ptr, 10);    /* convert digits string to numeric
                                            form (return type is long int) */

        if (errno == ERANGE) {
            printf("strtol: correct value outside range of representable"
                   " values.\n");
            exit(EXIT_FAILURE);
        }

        if (*ptr != '\0') {
            printf("strtol: unable to consume entire string.\n");
            exit(EXIT_FAILURE);
        }

        if (INT_MIN < r && r < INT_MAX)
            a[k].id = r;
        else {
            printf("%s is an invalid ID\n", digits);
            exit(EXIT_FAILURE);
        }


        b[0] = a[k].name, b[1] = a[k].degree, b[2] = a[k].campus;
        for (j = 0; j < 3; j++) {
            i = 0;
            while ((ch = getc(fp)) != '\n' && ch != EOF) {
                if (ch == ',')
                    break;
                if (i < NAME_LEN)
                    b[j][i++] = ch;
            }
            b[j][i] = '\0';    /* null-terminate string */
        }
    }
    fclose(fp);    /* close file */

    /* print values of structure members */
    for (i = 0; i < NUM_STUD; i++) {
        printf("ID    : %d\n", a[i].id);
        printf("Name  : %s\n", a[i].name);
        printf("Degree: %s\n", a[i].degree);
        printf("Campus: %s\n\n", a[i].campus);
    }

    return 0;
}

int num_digits(long n) {

    int digits = 0;

    while (n) {
        n /= 10;
        digits++;
    }

    return digits;

}

Output

ID    : 895329
Name  : Tom Elder
Degree: Computer Science
Campus: Downtown Campus

ID    : 564123
Name  : Elissa Honk
Degree: Interior Design
Campus: East Campus

ID    : 963474
Name  : Alfonso Dobra
Degree: Civil Engineering
Campus: West Campus

ID    : 127485
Name  : Paolo Morisa
Degree: Accounting
Campus: West Campus

ID    : 330021
Name  : Lisa Bali
Degree: Accounting
Campus: Downtown Campus

ID    : 844112
Name  : Eli Dovian
Degree: Computer Science
Campus: East Campus

ID    : 745112
Name  : Rola Etrania
Degree: Civil Engineering
Campus: East Campus

ID    : 541247
Name  : Pamela Dotti
Degree: Interior Design
Campus: East Campus

ID    : 745930
Name  : Paul Sabrini
Degree: Accounting
Campus: Downtown Campus

ID    : 500124
Name  : Gabriella Alma
Degree: Accounting
Campus: Downtown Campus

ID    : 741206
Name  : Joe Damian
Degree: Computer Science
Campus: West Campus

ID    : 963100
Name  : Perla Kino
Degree: Interior Design
Campus: East Campus
OTheDev
  • 2,916
  • 2
  • 4
  • 20