1

I want to read a simple data file, where the columns are separeted by a TAB.

P   C   D
4   1   4
5   2   5
20  4   20
...

I'm trying to do like this:

FILE *file_ptr = fopen(system_file, "r");
if (file_ptr == NULL) {
    printf("no such file.\n");
    return 0;
}

for (int i = 0; i < N_LINES; i++){
    fscanf(file_ptr, "%d %d %d ", &system.tasks[i].p, &system.tasks[i].c, &system.tasks[i].d);
}

But when i print the values from system.tasks it always returns 0 for all.

  • 2
    Always check the return value of any input function. – Cheatah Oct 23 '22 at 18:45
  • what is `system.tasks`? – Z4-tier Oct 23 '22 at 18:47
  • 1
    Aside: see [What is the effect of trailing white space in a scanf() format string?](https://stackoverflow.com/questions/19499060/what-is-the-effect-of-trailing-white-space-in-a-scanf-format-string) The format strign should be `"%d %d %d"` or `"%d%d%d"` but no final (white)space. – Weather Vane Oct 23 '22 at 18:51
  • 1
    The `fscanf` loop will get stuck at the non-integer values on the first line. Always check the return value, which here must be `3`. I suggest you read each line with `fgets()` and apply `sscanf()`. – Weather Vane Oct 23 '22 at 18:53
  • `while(i < N_LINES && fgets(buff, sizeof buff, file_ptr ) != NULL) { if(sscanf(buff, "%d%d%d", &system.tasks[i].p, &system.tasks[i].c, &system.tasks[i].d) == 3) i++; }` – Weather Vane Oct 23 '22 at 19:01
  • @Z4-tier It is like as in the answer – aaaaaaaaaaaaaaa Oct 23 '22 at 20:02

1 Answers1

3

You cannot tell what is happening because you do not test the return value of fscanf() so you do not know where the parsing fails.

You properly test for fopen failure, but you should provide a meaningful error message.

Furthermore, parsing a file with fscanf() is error prone because white space skipped when parsing numbers and strings may include line breaks.

It is much more reliable to read the file one line at a time with fgets() and parse the line with sscanf() (or other more precise functions such as strtol). Testing the return value of sscanf() is very important to detect invalid contents and prevent subsequent undefined behavior as the destination variables will not have been updated.

Here is a modified version using fgets() to read one line at a time:

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

struct task {
    int p, c, d;
    //...
};

#define N_LINES 100
struct system {
    struct task tasks[N_LINES];
    //...
} system;

int main() {
    char buf[256];
    const char *system_file = "system.txt";
    FILE *file_ptr = fopen(system_file, "r");
    if (file_ptr == NULL) {
        fprintf(stderr, "cannot open %s: %s\n", system_file, strerror(errno));

    int line = 0;
    // skip the header line
    fgets(buf, sizeof buf, file_ptr);
    line++;
    // parse the file contents
    for (int i = 0; i < N_LINES;) {
        line++;
        if (!fgets(buf, sizeof buf, file_ptr)) {
            fprintf(stderr, "%s:%d: missing records\n",
                    system_file, line);
            break;
        }
        if (sscanf(buf, "%d%d%d", &system.tasks[i].p,
                   &system.tasks[i].c, &system.tasks[i].d) == 3) {
            i++;
        } else {
            fprintf(stderr, "%s:%d: invalid format: %s", 
                    system_file, line, buf);
        } 
    }
    fclose(file_ptr);
    //...
    return 0;
}
chqrlie
  • 131,814
  • 10
  • 121
  • 189
  • 1
    Brilliant! Thank you for 1) mentioning the importance of checking the return value from "*scanf()", 2) Suggesting "fgets()" as an alternative, 3) suggesting "strerror()" to give a meaningful "file open error" diagnostic message. – paulsm4 Oct 23 '22 at 19:19
  • Nitpick: Note that ISO C does not require `fopen` to set `errno` on failure. Therefore, it is theoretically possible that `"cannot open input.txt: No error"` will be printed. However, I am unaware of any platform which does not set `errno` (POSIX requires this). In order to make the program also work on platforms that do not set `errno`, the program could set `errno` to `0` before calling `fopen` and then test whether `errno` is still `0` when `fopen` returns `NULL`. If that is the case, then a general error message could be printed instead of calling `perror` or `strerror`. – Andreas Wenzel Oct 24 '22 at 01:59
  • @AndreasWenzel: good point: the C Standard does not mandate setting `errno` on stream operation failures and indeed only defines very few error values for `errno`. The POSIX standard is much more consistent and any system that would not set `errno` on such failures would show poor quality of implementation. – chqrlie Oct 24 '22 at 05:17