Thread: Trying to read a file into an array using pointers

  1. #1
    Registered User
    Join Date
    Feb 2018
    Posts
    5

    Trying to read a file into an array using pointers

    I have a function that's trying to read a file into an array. The file simply consists of a single word on each line such as:

    Code:
    cat
    hat
    mat
    and so on. In my code below on the first pass of the for loop it correctly puts cat into wordArray[0] but on the second pass it replaces it with hat before the wordArray[i] assignment, then when the assignment actually happens, wordArray[1] is set to hat also. This same behavior continues for the other words in the file. I'm sure it's something to do with pointers but I can't figure out what exactly.

    Code:
    void readFile(char fName)
    {
    FILE *fNamePtr;
    int i;
    char word[7];
    char *wordArray[SIZE];
    
    fNamePtr = &fName; 
    
        if ((fNamePtr = fopen("myfile.dat", "r")) == NULL) 
        {
            printf("File could not be opened.");
        }
        else
        {
            for (i = 0; i < SIZE; i++)
            {
                fscanf(fNamePtr, "%s", word);
                wordArray[i] = word;
            }
        }
    }

  2. #2
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    Yes, it has to do with pointers. The issue is that on the first iteration of the loop, you set wordArray[0] to point to (the first character of) word. So, on the next iteration, the content of word is modified, hence wordArray[0] appears to change because what it points to has changed, even though your intent was to leave it alone and change wordArray[1].

    Rather, you should declare wordArray to be an array of arrays of char, then read into wordArray[i] directly.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  3. #3
    Registered User
    Join Date
    Feb 2018
    Posts
    5
    Thanks for the reply. I tried what you mentioned but I'm getting the same result... my new code:

    Code:
    void readFile(char fName)
    {
        FILE *fNamePtr;
        int i, j;
        char word[7];
        char *wordArray[SIZE][7]; // SIZE = 10
    
        fNamePtr = &fName; 
    
        if ((fNamePtr = fopen("myfile.dat", "r")) == NULL) 
        {
            printf("File could not be opened.");
        }
        else
        {
            for (i = 0; i < SIZE; i++)
            {
                for (j = 0; j < 7; j++)
                {
                    fscanf(fNamePtr, "%s", word);
                    wordArray[i][j] = word;
                }
            }
        }
    }
    I assume you mean 2d array when you say array of arrays? Also I'm assuming 2 loops are now needed. Regardless, I get the same result.

  4. #4
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    Array of arrays of char, not array of arrays of pointers to char. If you absolutely want to involve an array of pointers to char, you're going to have to use dynamic memory allocation or find some other way to allocate space for the different strings that you want to store.

    No, you don't need your nested loops. Your original loop will do. The idea is to read into an array of char rather than trying to read into an array of char and then assign it to a pointer.

    Also, get rid of word. You don't need it and it is just confusing you. You need it now because it is the only array of char that you have to store a string, but this also means that no matter how big your array of pointers is, you can only ever store one string because that's all the space you have for storing strings.
    Last edited by laserlight; 11-28-2018 at 01:49 PM.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  5. #5
    Registered User
    Join Date
    Feb 2018
    Posts
    5
    Ah I think I see now, I don't need wordArray to be a pointer. So I have updated my code to be

    Code:
    void readFile(char fName)
    {
        FILE *fNamePtr;
        int i, j;
        char wordArray[ROW][COLUMN];
    
        fNamePtr = &fName; 
    
        if ((fNamePtr = fopen("myfile.dat", "r")) == NULL) 
        {
            printf("File could not be opened.");
        }
        else
        {
            for (i = 0; i < ROW; i++)
            {
                for (j = 0; j < COLUMN; j++)
                {
                    fscanf(fNamePtr, "%s", wordArray[i][j]);
                }
            }
        }
    }
    However, on the first pass it now throws an exception of Access violation writing location in stdio.h. From what I'm reading it's because there is no memory allocated for wordArray[][] but is it not allocated in the line

    Code:
    char wordArray[ROW][COLUMN];
    ?

    Where ROW 10 and COLUMN 4
    Last edited by paddyMatches; 11-28-2018 at 02:50 PM. Reason: remove redundant code

  6. #6
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    I might have written the function like this:
    Code:
    int readFile(const char *filename, char wordArray[][7], size_t size)
    {
        size_t i;
    
        FILE *fp = fopen(filename, "r");
        if (!fp)
        {
            fprintf(stderr, "File named '%s' could not be opened.\n", filename);
            return 0;
        }
    
        for (i = 0; i < size; i++)
        {
            if (fscanf(fp, "%6s", wordArray[i]) != 1)
            {
                fprintf(stderr, "Could not read word from file named '%s'.\n", filename);
                fclose(fp);
                return 0;
            }
        }
    
        fclose(fp);
        return 1;
    }
    Then to call this function:
    Code:
    char wordArray[SIZE][7];
    if (readFile("myfile.dat", wordArray, SIZE))
    {
        /* do something with wordArray */
    }
    Notice that:
    • I provided the filename as an argument. You seemed to want to do this, but you only had a char as an argument, which isn't enough to store a filename. Furthermore, you hardcoded "myfile.dat" in the fopen call, whereas I provided the filename parameter as an argument to fopen.
    • I got rid of word.
    • I don't know where ROW and COLUMN came from, so I reverted to your use of SIZE in post #1, except that I provided it as an argument. By having a size parameter, I make this function flexible so that you can call it with word arrays of a variety of sizes.
    • I defined wordArray outside of the function, providing readFile with an identically named parameter (which is actually a pointer parameter, along it is meant to work like an array). This way, the caller can use wordArray, whereas in your original conception, there's no way for any code to work with wordArray, other than the readFile function itself and any functions it might call, but it is more sensible to call readFile to read from file into the word array, and then use the word array after leaving the readFile function.
    • I checked the return value of fscanf, and also gave the string format specifier a field width, i.e., "%6s" instead of just "%s" as the latter makes you vulnerable to buffer overflow.
    • I gave readFile an int return type as a boolean return type so that the caller can more easily check whether or not the reading of the file succeeded. I also printed the error messages to stderr: you might find this useful to separate "ordinary" output from error messages by piping standard output and standard error output separately.
    • I ensured that I called fclose appropriately.
    • I changed i to be a size_t instead because that is the usual type for array sizes. You can keep it as an int if you prefer, but generally sizes should not be negative, and int is signed whereas size_t is unsigned (and is the type of the result of sizeof).

    Another improvement you could make is to get rid of the hardcoded magic number 7, replacing it with a named constant.
    Last edited by laserlight; 11-28-2018 at 03:09 PM.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  7. #7
    Registered User
    Join Date
    Feb 2018
    Posts
    5
    Thank you for the effort and I don't mean to come across ungrateful but that reads as a re-write of my general syntax and not addressing my problem.

  8. #8
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    Quote Originally Posted by paddyMatches
    I don't mean to come across ungrateful but that reads as a re-write of my general syntax and not addressing my problem.
    Yes, it is intended as an example of what I might write based on what you attempted, not a fix of your problems per se. The idea is for you to read the code and my comments, and from there understand what you should have done.

    If you really want to address your problem directly rather than try to understand my example so as to learn from it, let's put your function into a test program:
    Code:
    #include <stdio.h>
    
    #define ROW 10
    #define COLUMN 4
    
    void readFile(char fName)
    {
        FILE *fNamePtr;
        int i, j;
        char wordArray[ROW][COLUMN];
     
        fNamePtr = &fName; 
     
        if ((fNamePtr = fopen("myfile.dat", "r")) == NULL) 
        {
            printf("File could not be opened.");
        }
        else
        {
            for (i = 0; i < ROW; i++)
            {
                for (j = 0; j < COLUMN; j++)
                {
                    fscanf(fNamePtr, "%s", wordArray[i][j]);
                }
            }
        }
    }
    
    int main(void)
    {
        readFile('x');
        return 0;
    }
    Now we compile your code with gcc 5.4.0:
    Code:
    test.c: In function ‘readFile’:
    test.c:12:14: warning: assignment from incompatible pointer type [-Wincompatible-pointer-types]
         fNamePtr = &fName; 
                  ^
    test.c:24:34: warning: format ‘%s’ expects argument of type ‘char *’, but argument 3 has type ‘int’ [-Wformat=]
                     fscanf(fNamePtr, "%s", wordArray[i][j]);
    We see two warnings. The first warning tells you that you made a nonsensical assignment:
    Code:
    fNamePtr = &fName;
    fName is a char, so &fName is a pointer to char. A pointer to char has nothing to do with a pointer to FILE, yet you're trying to assign &fName to fNamePtr despite fNamePtr being a pointer to FILE. In fact, you can safely delete this line, especially since you immediately assign to fNamePtr after this.

    Next, and more importantly, the second warning tells you that you made an incorrect use of fscanf. You want to read into wordArray[i][j], and used the %s format specifier. %s is used to read strings, but wordArray[i][j] is a char. Therefore, you have a mismatch in type.

    Actually, you don't want to read into wordArray[i][j], and if you did, you would use %c with &wordArray[i][j]. Rather, you want to read into wordArray[i], so you should have written:
    Code:
    fscanf(fNamePtr, "%s", wordArray[i]);
    Furthermore, as I mentioned twice already, you don't need the nested loops.

    Therefore, we can see that your problem is that you did not compile at a sufficiently high warning level and then did not heed the warnings. You went on to find that your program did not work, but of course since you did not heed a warning that actually mattered.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  9. #9
    Registered User
    Join Date
    Feb 2018
    Posts
    5
    Thanks. I was just logging in to say I found the issue in the fscanf line after some trial and error. Now I know why it was failing.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Replies: 7
    Last Post: 12-07-2012, 10:44 PM
  2. read from file to array. about fin.eof() \n EOF...
    By hth373737 in forum C++ Programming
    Replies: 9
    Last Post: 03-26-2009, 12:47 PM
  3. Read file into array
    By neobarn in forum C Programming
    Replies: 6
    Last Post: 01-09-2009, 12:41 PM
  4. Read in binary file ( pointers )
    By Giant in forum C Programming
    Replies: 41
    Last Post: 06-23-2005, 04:54 AM
  5. read from .txt file & put into array?
    By slow brain in forum C Programming
    Replies: 6
    Last Post: 02-25-2003, 05:16 AM

Tags for this Thread