0

So my program takes a file as input and parses out spaces. Tokens are saved into an array. I have a function to print out the contents of the array to test if the parser works. The code compiles with gcc -o filename filename.c . But when I run the program and give it a filepath, I get a pop up window indicating filename.exe has stopped working : A problem caused the program to stop working correctly. Windows will close the program and notify you if a solution is available.

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
char file_name[100];
char *token_array[256];
int main()
{
   char ch;
   FILE *fp;

   int n=0;
   char *str;

   printf("Enter filepath\n");
   gets(file_name);

   fp = fopen(file_name,"r"); 

   if( fp == NULL )
   {
      perror("Error opening the file.\n");
      exit(EXIT_FAILURE);
   }

   int i = 0;         
   char *p; 
   while( ( ch = fgetc(fp) ) != EOF )
   {
    char *fgets(char *str, int n, FILE *stream);
    p = strtok(str, " ");
    i=0;

    while(p!=NULL)
    {
        strcpy(token_array[i], p);
    }

    i++;

   }

   for(n=0;n<256;n++)
   {
      printf("%s", &token_array[n]);
   }

   return 0;
}
user3002315
  • 91
  • 1
  • 6
  • The question is obviously about programming. It's just that it's apparently from a novice programmer trying to learn C, and who is getting no help from the almost-useless Windows error popup dialog. – Andrew Henle Jun 04 '16 at 01:56
  • 2
    when compiling, always enable all the warnings, then fix those warnings. You should have gotten a warning that `gets()` is depreciated (and completely removed in C11) There are a number of lesser warnings that will also be output by the compiler having to do with implicit conversion of numeric values. `For `gcc`, at a minimum use: `-Wall -Wextra -pedantic` I also use: `-Wconversion -std=gnu99` ) – user3629249 Jun 04 '16 at 02:39
  • 1
    for ease of readability and understanding, 1) separate code blocks (for, if, else, while, do...while, switch, case, default) via a blank line 2) consistently indent the code. Never use tabs for indenting. Suggest indent 4 spaces after every opening brace '{'. Un indent before every closing brace '}'. 4 spaces is wide enough to be visible even with variable width fonts and is easy for the human eye to recognize. – user3629249 Jun 04 '16 at 02:43
  • 1
    this code block: ` while(p!=NULL) { strcpy(token_array[i], p); }` is not incrementing the variable `i`, so all the tokens are placed at the same offset into the array: `token_array[]`. – user3629249 Jun 04 '16 at 02:44
  • 1
    this code sequence: ` while( ( ch = fgetc(fp) ) != EOF ) { char *fgets(char *str, int n, FILE *stream);` will toss away the first character of every line in the file. – user3629249 Jun 04 '16 at 02:47
  • this line: `char *fgets(char *str, int n, FILE *stream);` is nonsence. your declaring a `char *` as what the function: `fgets()` returns, but your not assigning that pointer to any variable. Strongly suggest removing the call to `fgetc()` and place the call to `fgets()` in its' place – user3629249 Jun 04 '16 at 02:49
  • to save yourself lots of 'gray hairs' in the future (because the error of only typing one `=` rather than two: `==` changes the comparison in to an assignment, not what you usually want for lines like: `if( fp == NULL )` Strongly suggest getting into the habit of placing the literal on the left, I.E. `if( NULL == fp )` so if you happen to only type a single `=`, the compiler will catch the error immediately, rather than you spending hours and hours debugging the code. – user3629249 Jun 04 '16 at 02:53
  • the token array is declared as 256 pointers to char arrays. However, this line: `strcpy(token_array[i], p);` is trying to copy characters over those pointers. Strongly suggest using: `token_array[i] = strdup(p);` – user3629249 Jun 04 '16 at 02:56
  • the file is being read, 1) read first char of a line 2) read the rest of the line, However, after reading a line, the call to `strtok()` needs (on the first call with the new line) to be: `p = strtok( str, " " );` then, in a loop, p = strtok( NULL, " " );` so this code block: `while(p!=NULL) { strcpy(token_array[i], p); }` should be: `while( i < 256 && p != NULL ) { token_array[i] = strdup(p); i++; p = strtok( NULL, " " ); } – user3629249 Jun 04 '16 at 02:59

2 Answers2

1

This line does nothing:

char *fgets(char *str, int n, FILE *stream);

As written, that line is nothing but a declaration of a function, probably not what you want.

If you're going to use fgets(), use fgets() alone and don't mix it with fgetc():

char str[1024];
while ( fgets( str, sizeof( str ), fp )
{
   .
   .
   .

fgets() returns NULL upon EOF or an error condition. And note that str is not a char *, it's a char array. An even better solution would be to use getline() because fgets() with a fixed-length buffer can only read entire lines that fit into the buffer. Longer lines will be split.

fgetc() returns int, not char. But you don't shouldn't use fgetc() when reading lines from a file with fgets(). Pick one and use that. But if you use fgetc(), you'll need to write code to put together each line as you read it character-by-character.

And what does token_array[i] point to? You declare token_array as an array of pointers:

char *token_array[256];

But you never allocate any memory for each pointer in the array to point to.

The easiest solution is to change

strcpy(token_array[i], p);

to

token_array[i] = strdup(p);

assuming your platform has strdup(), which is equivalent to a malloc() and strcpy() to the memory returned by malloc(), so you need to call free() on the string you get from strdup().

Your use of strtok() is wrong. See How does strtok() split the string into tokens in C? for examples of proper strtok() usage.

And since token_array is an array of char * pointers, this

printf("%s", &token_array[n]);

will take the address of the actual pointer itself, and not the string it's supposed to point to. It will then try to print out a "string" in the memory containing the pointer variable. That's won't work well. Since it's already a char *, all you need is this:

printf("%s", token_array[n]);
Community
  • 1
  • 1
Andrew Henle
  • 32,625
  • 3
  • 24
  • 56
  • I tried the code, and I also needed to trim the filename, since it reads a newline that is not part of the filename (please look at my answer for details). – Niklas Rosencrantz Jun 04 '16 at 02:01
  • I tried your suggestions, and now my control prompt is just frozen. Is there an infinite loop somewhere? – user3002315 Jun 04 '16 at 02:14
1

You can try this code instead. I changed your program so that it can read and tokenize the file.

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

char *trim(char *s) {
    int i = strlen(s) - 1;
    if (s[i] == '\n')
        s[i] = '\0';
    return s;
}

#define BUFFER_SIZE 100
char *token_array[256];
int main( int argc, char** argv ){

    const char *delimiter_characters = " ";
    char *filename =  malloc(BUFFER_SIZE);

    int i = 0;
    printf("Enter filepath\n");
    fgets(filename, 100, stdin);


    FILE *input_file = fopen( trim(filename), "r" );
    char buffer[ BUFFER_SIZE ];
    char *last_token;

    if( input_file == NULL ){

        fprintf( stderr, "Unable to open file %s\n", filename );

    }else{

        // Read each line into the buffer
        while( fgets(buffer, BUFFER_SIZE, input_file) != NULL ){

            // Write the line to stdout
            //fputs( buffer, stdout );

            // Gets each token as a string and prints it
            last_token = strtok( buffer, delimiter_characters );
            while( last_token != NULL ){
                //printf( "%s\n", last_token );
                token_array[i] = malloc(100);
                strcpy(token_array[i], last_token);
                i++;
                last_token = strtok( NULL, delimiter_characters );
            }

        }

        if( ferror(input_file) ){
            perror( "The following error occurred" );
        }

        fclose( input_file );

    }
    int n;
    for(n=0;token_array[n] != NULL && n<256;n++)
    {
        printf("%s", token_array[n]);
        free(token_array[n]);
    }
    free(filename);
    return 0;

}

data.txt

Hello Foo Bar
How are you?

Test

Debug/gnu
Enter filepath
data.txt
HelloFooBar
Howareyou?
Process finished with exit code 0

The contents of the array are

n[0] Hello
n[1] Foo
n[2] Bar

n[3] How
n[4] are
n[5] you?

You can see the contents of the array if you add debug info:

printf("n[%d] %s\n", n, token_array[n]);

Niklas Rosencrantz
  • 25,640
  • 75
  • 229
  • 424
  • 1
    Correct me if I'm wrong, but this seems to put "HelloFooBar" into a[0] and "Howareyou?" into a[1]. But I want Hello to be a[0], Foo to be a[1]. – user3002315 Jun 04 '16 at 02:17
  • 1
    @user3002315 I updated my answer to show you that Hello is the first element and Foo is the second element. It think it should be what you want if you try it. – Niklas Rosencrantz Jun 04 '16 at 03:01
  • 1
    Just a note, in `trim` in `if ((i > 0) && (s[i] == '\n'))`, the check for `(i > 0)` isn't necessary, if `strlen` returns `0`, `s[0]` is already `0` and there is no harm done setting `s[0] = '\0';` Your call. – David C. Rankin Jun 04 '16 at 03:10
  • @DavidC.Rankin I now made the changes you suggested and added calls to `free`(without recompiling and when I make changes without recompiling I usually break my code...) – Niklas Rosencrantz Jun 04 '16 at 03:34
  • @Programmer400 if I wanted to add tab and newline as delimiters as well, is there a quick fix? – user3002315 Jun 04 '16 at 06:08
  • @user3002315 Yes, just change the `delimiter` to several: `const char *delimiter_characters = " \n\t";` – Niklas Rosencrantz Jun 04 '16 at 06:47