1

I am doing a small project for college (1st semester doing a bookstore implementation) and I have a problem with reading from a text file into a list of structs, with a two-dimensional array of characters in it that stores authors. However, it doesn't work properly (every time I launch the program it shows list is empty). Writing to a file works (I think, because it overwrites my text file with empty data).

Example data:

Adam Mickiewicz///Pan Tadeusz/Publisher 1/1833/24.99
Jules Verne///Around The World in 80 days/Publisher 1/1904/19.99
Jean-Jacques Sempe/Rene Goscinny//Little Nicholas/Publisher 2/1963/22.99

My structure:

#define AK 3 // Constant denoting max number of authors

typedef struct
{
    char authors[AK][100];
    char title[255];
    char publisher[100];
    unsigned int year;
    double price;

    struct Book *next;
} Book;

Book *first; // Address of the first element in a list

Reading from file:

void read_from_file(const char *path)
{
    int no_of_authors;
    int i; 
    printf("Loading...\n"); 
    FILE *fp = fopen(path, "r"); // Opening a file

    // Checking for errors
    if (!fp) {
        printf("Error reading from file!");
        return;
    }

    // The loading mechanism 
    no_of_authors = 0;
    while (!feof(fp)) {
        Book *new = (Book*) malloc(sizeof(Book));
        for (i = 0; i < AK; i++) {
            fscanf(fp, "%s/", new->authors[i]);
        }
        fscanf(fp, "%s/%s/%u/%lf", new->title, new->publisher,
                &new->year, &new->price);
        fscanf(fp, "\n");
        new = new->next;
    }

    fclose(fp);
    printf("Loading successful.");
}

Writing to file (just in case):

void write_to_file(const char *path, Book *first)
{
    int i;
    printf("Saving...\n");
    FILE *fp = fopen(path, "w");
    Book* current = first;

    if (!fp) {
        printf("Error opening the file!");
        dump_list(first); // Dumping the list to prevent memory leaks, this works okay
    }
    // Saving mechanism
    while (first != NULL) {
        for (i = 0; i < AK; i++) {
            fprintf(fp, "%s/", current->authors[i]);
        }
        fprintf(fp, "%s/%s/%u/%lf", current->title, current->publisher,
                &current->year, &current->price);
        fprintf(fp, "\n");
    }

    fclose(fp);
    printf("Saved successfully");
}
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
DJ_Wilde
  • 27
  • 4
  • 3
    Thing get easier if you count cents in integer for price, instead of dollars in a double (or similar currency and subcurrency). – Yunnosch Dec 31 '17 at 13:27
  • 1
    `new = new->next;` : where do you give a value to `new->next` ? – cdarke Dec 31 '17 at 13:28
  • 1
    Probably interesting read: https://stackoverflow.com/questions/5431941/why-is-while-feof-file-always-wrong?s=1|141.6435 – Yunnosch Dec 31 '17 at 13:29
  • I wanted to move to the next element and to allocate memory to it. – DJ_Wilde Dec 31 '17 at 13:32
  • 4
    struct Book {}; is different from struct {} Book; – stark Dec 31 '17 at 13:34
  • What is the difference between them? – DJ_Wilde Dec 31 '17 at 13:51
  • One defines a name for a struct definition and the other is a struct variable. – stark Dec 31 '17 at 13:56
  • 4
    `while (!feof(fp))` is wrong: it tells you the last read did not fail but does not tell you the next read will not fail. – Paul Ogilvie Dec 31 '17 at 14:02
  • `while (!feof(fp))` and `Book *new = (Book*) malloc(sizeof(Book));` imply an outdated text/prof on learning C. – chux - Reinstate Monica Dec 31 '17 at 14:16
  • 2
    `typedef struct {...} Book;` defines anonymous struct type, and typedefs it to type name `Book`. `struct Book *next;` defines an opaque pointer to type `struct Book`, which you never define (but this is ok, because it is a pointer and doesn't need to know the size). In general, avoid `typedef` with structs, because while it saves some typing, it also creates confusion (like demonstrated here). – hyde Dec 31 '17 at 15:42
  • 1
    Another thing, as stated in an answer below: always check return values of scanf functions (all file reading functions really, but with scanf it is especially important, because in addition to IO error or EOF, there can be parse errors). Fix these first, before trying to solve any misbehavior. – hyde Dec 31 '17 at 15:44
  • in the definition of the struct Book. the definition is missing a 'tagname' immediately after the token `struct`. And this field: `struct Book *next;` is wrong as it is referencing a name `Book` which is a `typedef` that does not 'yet' exist. – user3629249 Jan 02 '18 at 06:10
  • Never use: `while( !feof(..) )` because the function: `feof()` does not do what you seem to be expecting. it is best to not use the name `new` because if the compiler can also handle C++, then the compiler will produce the wrong code – user3629249 Jan 02 '18 at 06:16
  • when calling any of the `scanf()` family of functions: 1) always check the returned value to assure the operation was successful. 2) when using the input/format specifiers: `%s` or `%[...], always include a max characters modifier that is one less than the length of the input buffer, because those specifiers always append a NUL byte to the input. Using the max characters modifier avoids any possibility of a buffer overflow. Such overflow is undefined behavior and can lead to a seg fault event – user3629249 Jan 02 '18 at 06:27
  • error messages should be output to `stderr`, not `stdout` and when the error is from a system function, should also output the associated text that tells why the system thinks the function failed. The function: `perror()` properly handles that activity. – user3629249 Jan 02 '18 at 06:29
  • when calling any of the heap allocation functions: (malloc, calloc, realloc), 1) always check (!=NULL) the returned value to assure the operation was successful. 2) the returned type is `void*` so can be assigned to any pointer. Casting just clutters the code, making it more difficult to understand, debug, etc. – user3629249 Jan 02 '18 at 06:31
  • in one of the calls to `fscanf()`, this format string: `"%s/"` will not do what you are expecting. Suggest: `"%[^/]"`. Remember my earlier comment about including a max characters modifier – user3629249 Jan 02 '18 at 06:34
  • none of the posted code calls `free()` on each of the pointers returned from `malloc()` so the program contains a massive memory leak. – user3629249 Jan 02 '18 at 06:36
  • this format string: `"%s/%s/%u/%lf"` will not perform the desired functionality. This is (mostly) because the first `%s` will consume up to a space or newline or EOF That is not what you are wanting to do. Suggest using something similar to: `"%[^/]/%[^/]/%u/%lf"` and remember my earlier comment about max character modifiers. Also, this will NOT (even after the modifications) properly parse the data from the input file because the input file contains some consecutive `/` characters. – user3629249 Jan 02 '18 at 06:40
  • Hmmm, Just noticed 13 results with this search [Adam Mickiewicz](https://stackoverflow.com/search?q=Adam+Mickiewicz) – chux - Reinstate Monica Jan 03 '18 at 17:17
  • @DJ_Wilde Do not significantly change the questions after answers arrives - post rolled back. You can post your own answer if you like - even accept it. [This page](https://stackoverflow.com/help) may help provide good SO etiquette. – chux - Reinstate Monica Jan 03 '18 at 17:21

1 Answers1

3

OP's biggest failing is not checking the return value of fscanf(). Had code done so, problems would be more rapidly detected.


When is comes to reading lines of data the first consideration is:

Could input be faulty?

With learner applications this is often considered no. Input is either "good" or end-of-file. Let us not assume data is too well formated.

As it turns out, while the data file may not be faulty, the code reading it may be wrong. The subtle 2nd reason for code to check the *scanf() return values - self validation.


For line orientated data, it is much better to read is a line of data with fgets() than feof(), fscanf()... See also @Paul Ogilvie

 char buf[sizeof(Book) * 2]; // Use an ample sized buffer
 while (fgets(buf, sizeof buf, fp)) {

Use "%s" to read in text that does not include white-space. This will also read in '/'. Since '/' is use to delimit, "%s" is not an acceptable input specifier. Further names like "Adam Mickiewicz" include a space. A 2nd reason to not use "%s".

Consider what fscanf(fp, "%s/", new->authors[i]); is doing. "%s" scans into new->authors[i] non-white-space characters. A character after non-white-space characters is a white-space, never a '/'.

Use "%[^/]" to read in text that does not include '/'.

Use "%n" to keep track of the current scan offset.

Now parse the line.

    char *p = buf;
    Book *nu = malloc(sizeof *nu);  // no cast needed
    for (size_t i = 0; i < AK; i++) {
      int n = 0;
      sscanf(p, "%99[^/]/%n", nu->authors[i], &n);
      if (n == 0) {
        Handle_Parse_Error();
      }
      p += n; 
    }
    if (sscanf(p, "%254[^/]/%99[^/]/%u/%lf", 
        nu->title, nu->publisher, &nu->year, &nu->price) != 4) {
      Handle_Parse_Error();
    }

Robust code would add checks on each member. Suit to coding goals.

    if (nu->year < -6000 || nu->year > 2999) Fail_Year_Range();

Further work is needed to link data together. OP's code is unclear on this matter and so left to OP. A possible approach:

Book *read_from_file(const char *path) {
  Book first;  // Only the .next member is used.
  first.next = NULL;
  Book *current = &first;

  // setup while () loop
  ...
  while (fgets(buf, sizeof bu, fp)) {
    ...
    Book *nu = malloc(sizeof *nu);
    nu->next = NULL;
    ...
    // parse data, fill in rest of nu->
    ....
    current->next = nu;  // update current pointer
    current = nu;
  }
  // close up file ...

  return first.next;
}
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256