0

I have a file which contains information about films like this:

Film code
Name
Year of release
Movie length(in minutes)
The film producer

I have to read this info from a file and store that info into pointers. My code so far:

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

typedef struct filmiab
{
    int koodpk;
    char *nimed;
    int aasta;
    int kestus;
    char *rezi;
} filmiab;

int main()
{
    filmiab *db;

    FILE *f1;
    f1 = fopen("filmid.txt", "r");

    db->nimed = (char*)malloc(sizeof(db->nimed) * sizeof(char));
    db->rezi = (char*)malloc(sizeof(db->rezi) * sizeof(char));

    while(1)
    {
        fscanf(f1, "%d ", &db->koodpk);
        fgets(db->nimed, 100, f1);
        db->nimed = (char*)realloc(db->nimed, sizeof(char) * sizeof(db->nimed)); //gets more memory to store the strings
        fscanf(f1, "%d %d ", &db->aasta, &db->kestus);
        fgets(db->rezi, 100, f1);
        db->rezi = (char*)realloc(db->rezi, sizeof(char) * sizeof(db->rezi));

        printf("Filmi kood: %d\nFilmi nimi: %sAasta: %d\nKestus minutites: %d\nFilmi rezis66r: %s\n",
        db->koodpk, db->nimed, db->aasta, db->kestus, db->rezi);
        printf("\n");
    }

    return 0;
}

It just goes into an infinte loop and only prints the last 5 lines. I know that when using fgets it replaces all the strings with the last 5 lines. But what can I do so it would store all the info and that so I could print them out (or just use them) in another function. And why does it go into an infinite loop ?

EDIT: I have to use only the pointers that are created in the struct.

EDIT2: Now both these lines fgets(db->nimed, 100, f1); fgets(db->rezi, 100, f1); store the required info and the blank spaces. What to do so it only stores the names of the films and the producers.

  • 2
    1) `filmiab *db;` --> `filmiab db;` 2) `char *nimed;` --> `char nimed[100];` – BLUEPIXY Apr 20 '17 at 18:09
  • It just give an error: invalid operands to binary. But that isn't the case here. The problem is in the while loop. EDIT: I have to use pointers - it is one of requirements(school homework). – Sander Ts. Apr 20 '17 at 18:11
  • Please set the appropriate memory block in the pointer before using the pointer. – BLUEPIXY Apr 20 '17 at 18:16
  • 1
    Exit example of loop : `if(EOF==fscanf(f1, "%d ", &db->koodpk)) break;` – BLUEPIXY Apr 20 '17 at 18:17
  • I have done that, I just have left that part out. – Sander Ts. Apr 20 '17 at 18:17
  • Also, when you make this call: `fgets(db->nimed, 100, f1);` you need to make sure to allocate a block of memory for `nimed`. – bruceg Apr 20 '17 at 18:19
  • You also left out, or perhaps never used, any library `#include` header files. Please post the [Minimal, Complete, and Verifiable example](http://stackoverflow.com/help/mcve) that shows the problem. – Weather Vane Apr 20 '17 at 18:20
  • `filmiab *db; ... fscanf(f1, "%d ", &db->koodpk);` --> `&db->koodpk` is invalid as `db` is not assigned prior to `db->koodpk`. – chux - Reinstate Monica Apr 20 '17 at 18:20
  • `sizeof(db->nimed)` and `sizeof(db->rezi)` are the size of *pointers*. Nowhere is there a memory allocation for the 100 length array which you later assume is there. That magic number `100` should be a `const` or `#define`. And the `realloc` asks for the same memory as with `malloc`: no use resizing *after* reading the input. – Weather Vane Apr 20 '17 at 18:31
  • Thank you BLUEPIXY, the exit example worked - now it isn't in infinite loop anymore and it stores correctly. – Sander Ts. Apr 20 '17 at 18:32
  • We were thought that in fgets(db->rezi, 100, f1), it reads only the 100 first characters. If the films name is less than 100 characters, it stops reading. Is this wrong or I've misunderstood him ? – Sander Ts. Apr 20 '17 at 18:37
  • That is right, but as previous comment, and one answer say, there is not enough memory allocated to place it. – Weather Vane Apr 20 '17 at 18:40
  • I would suggest running your code under valgrind to check all the memory allocations and accesses are done correctly. Also, use your compiler, for example `gcc` warns about that unitialized pointer instantly if `-Wall` is enabled. – ilkkachu Apr 20 '17 at 18:48

1 Answers1

3

It just goes into an infinte loop

That's because it is an infinite loop. You have a while(1) with no break condition. It should break after it can no longer read any lines.

Every time you work with a file, that means fopen, fgets, and fscanf, you need to check whether the operation succeeded. If it fails, the code will continue with whatever garbage is the result.

This is especially a problem with fscanf because if it fails, it leaves the file pointer where it was, and might continuously rescan the same line over and over again. In general, avoid scanf and fscanf. Instead, fgets the whole line, to ensure it gets read, and scan it with sscanf.


The other problem is how you're allocating memory isn't right.

filmiab *db;

This puts a pointer on the stack, but it points to garbage. No memory has been allocated for the actual struct.

db->nimed = (char*)malloc(sizeof(db->nimed) * sizeof(char));

sizeof(db->nimed) is not the length of the string in db->nimed, but the size of the pointer. Probably 4 or 8. So you've only allocated 4 or 8 bytes.

fgets(db->nimed, 100, f1);

Then you read up to 100 bytes into it with fgets, probably causing a buffer overflow.

db->nimed = (char*)realloc(db->nimed, sizeof(char) * sizeof(db->nimed));

Then you reallocate too little, too late. Again, same as before, this is allocating only 4 or 8 bytes. It's probably doing nothing because the memory was already this size.


To fix this, start by putting the whole struct on the stack.

filmiab db;

Then allocate the necessary space for its strings. Note that since sizeof(char) is always 1 there's no need to include it. There's also no need to cast the result of malloc.

db.nimed = malloc(100);
db.rezi = malloc(100);

Now there's no need to realloc, you've got you 100 bytes of memory and can write to it with fgets.


For future reference, here's how I'd rework this.

int main() {
    filmiab db;

    char file[] = "filmid.txt";
    FILE *f1 = fopen(file, "r");
    if( f1 == NULL ) {
        fprintf( stderr, "Could not open %s for reading: %s", file, strerror(errno) );
    }

    char line[1024];
    int state = 0;

    while(fgets(line, 1024, f1) != NULL) {
        switch(state % 5) {
            case 0:
                sscanf(line, "%d", &db.koodpk);
                break;
            case 1:
                db.nimed = strdup(line);
                break;
            case 2:
                sscanf(line, "%d", &db.aasta);
                break;
            case 3:
                sscanf(line, "%d", &db.kestus);
                break;
            case 4:
                db.rezi = strdup(line);
                printf("Filmi kood: %d\nFilmi nimi: %sAasta: %d\nKestus minutites: %d\nFilmi rezis66r: %s\n",
                        db.koodpk, db.nimed, db.aasta, db.kestus, db.rezi);
                printf("\n");
                break;
            default:
                // Should never get here
                assert(0);
                break;
        }

        state++;
    }

    return 0;
}

There's a single, large line buffer which gets reused, it's 1K but it's only 1K once. strdup duplicates strings, but only allocates enough memory to hold the string. This eliminates the need to predict how big lines are, and it also avoids fragmenting memory with a lot of reallocs.

In this particular case, since db is being reused, it would be more optimal to just allocate 1024 each for db.nimed and db.rezi, but I wanted to demonstrate the more general case where the stuff read in will stick around.

while(fgets(line, 1024, f1) != NULL) ensures I'll read until the end of the file. Then line is processed using the switch statement depending on what sort of line is coming next. This separates the process of reading from a file, which can be unpredictable and needs a lot of error checking, from processing the data which is a bit easier. Technically I should be checking if those sscanfs succeeded, but I got lazy. :)

Community
  • 1
  • 1
Schwern
  • 153,029
  • 25
  • 195
  • 336
  • The task was to get a certain amount of memory and then use realloc when needed. The point is that I have to use realloc. – Sander Ts. Apr 20 '17 at 18:39
  • @SanderTs. please take 5 and re-read the comments and answer from the top. The use of `realloc` is to shrink the *unnecessary* memory allocated, not to expand inadequate allocation, from the way your code is written, although not enough was allocated originally. – Weather Vane Apr 20 '17 at 18:42
  • @SanderTs. This isn't a good task for `realloc`. You *could* use it to shrink `db.nimed` and `db.rezi` down to their minimum size using `strlen` ***after*** the `fgets`, but that's an inefficient way to read from a file, and you need them to be 100 bytes again for the next go around the loop, so no point in shrinking it. When reading in lines from a file, generally you allocate one large buffer, like `char line[1024]`, and reuse that with `fgets( line, 1024, fp )`. Then you process `line` and copy its parts into the right size memory. – Schwern Apr 20 '17 at 18:44
  • I got the infinite loop problem fixed. But is there a way to get the needed memory by somehow scanning the whole file and then allocate the needed memory. Because when the teacher is checking the program, he might use another file which may or may not have more or less than 100 different movie names. – Sander Ts. Apr 20 '17 at 18:53
  • @SanderTs. Yes, but it's not a good idea. You'd have to read each line twice. Once, character by character, to determine how big it is, but you can't store anything because you haven't allocated the memory. Then you'd `fseek` back to the beginning of the line, allocate the exact amount of memory, and read the line again. This is extremely inefficient, disk I/O is slow. You're better off allocating a single, reusable line buffer, reading into that, and determining how much memory you need from that line buffer. – Schwern Apr 20 '17 at 18:56
  • @SanderTs. "*he might use another file which may or may not have more or less than 100 different movie names.*" Since you're not storing the movie information in a list, you keep overwriting the same `db` memory, how big the file is does not matter. You'll just keep overwriting `db`. Your only limit right now is the name and producer lines can only be 99 characters long. I think you might need to step back and ask a new question about the whole task. – Schwern Apr 20 '17 at 18:58
  • Yeah, I might have misunderstood the task a little bit. But thank you all for the help ! – Sander Ts. Apr 20 '17 at 19:02