LIsa
LIsa

Reputation: 163

C fscanf to read each word from a file not working

I already know how read word by word from a file (using fgets then strok each other), however itd like to find the simplest way and from what Ive seen fscanf, should work.

If fscanf will allocate the pointer of a word inside array[i], why is it not storing anything.

Natural Reader is
john make tame
michael george meier
Bonus Second pass

Im expecting

word = Natural
word = reader
word = is
word = john
...
word = pass
int main(int argc, char *argv[]) {
   FILE *file = fopen(argv[1], "r");
   int ch;
   int count = 0;
   while ((ch = fgetc(file)) != EOF){
      if (ch == '\n' || ch == ' ')
         count++;
   }
   fseek(file, 0, SEEK_END);
   size_t size = ftell(file);
   fseek(file, 0, SEEK_SET);
   char** words = calloc(count, size * sizeof(char*) +1 );
   int i = 0;
   int x = 0;
   char ligne [80];

   while(fscanf(file, "%s", words[i]) != EOF ){ //or != 1
      printf("%s\n", words[i]);
      i++;
   }

   free(words);
   fclose(file);
   return 0;
}

Upvotes: 0

Views: 173

Answers (2)

anastaciu
anastaciu

Reputation: 23822

char** words = calloc(count, size * sizeof(char*) +1 ); is not what you'd want, size is the number of total bytes in the file, by using sizeof(char*) you are multiplying the size you need by the size of a pointer, which will likely give you 8 + 1 times more space than you need, take a good look at calloc manual, the first parameter is the number of items, the second is the size of each item.

It will also not give you a 2D array, for that you would need to get the size of each word and allocate each line with the needed space, this would be an obvious overkill just to print the words.

If you want to to read a file to the end word by word and only print the words, you don't need all that, you can use only fscanf:

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

int main(int argc, char** argv) {
    
    if(argc > 1){

        FILE* file = fopen(argv[1], "r"); 
        
        if(file == NULL){
            return EXIT_FAILURE;       
        }

        char word[100];
    
        while(fscanf(file, "%99s", word) > 0){
            printf("%s\n", word);
        }

        fclose(file);
    }
}

Note that you should validate fopen return and also check the number command line arguments, if you are to use malloc, also check its return value.

Notice that I added a width specifier in fscanf, this avoids potential buffer overflow.

Upvotes: 2

Anton Bernatskiy
Anton Bernatskiy

Reputation: 197

Short answer: because calloc does not support allocating memory for 2D arrays and the words pointer ends up not working as expected.

Long answer: the call

char** words = calloc(count, size * sizeof(char*) +1 );

allocates some memory, fills it with zeros and stores its address at words. The value of words[i] is then defined as "whatever sits in the memory at the position words+i*sizeof(char*)". In your case that is zeros, which become NULL when they are interpreted as a pointer. fscanf requires some valid memory to store whatever it fetches from the file, so when it gets words[i] it detects that it's a NULL and refuses to write anything there. Then you read from there with printf and get a segfault.

For your code to work as intended, you need to dynamically allocate a 2D array of chars. That can be done either by allocating a 1D array of pointers to char and populating it with pointers to 1D arrays of chars (see methods 2 and 4 here), or by using variable length arrays (Jens Gustedt's answer here).

Or, you could try to avoid using a dynamic 2D array in C. That's what I do unless I absolutely have to. For example, you could replace char words[X][Y] with a 1D array char words[X*Y], then obtain ith string of the array as words+i*Y.

Upvotes: 1

Related Questions