2

I'm trying to read a file, which contains three lines of text, and store its contents into an array.

It should be rather straightforward, but I keep getting an "Exception: Access Violation", as per title.

This is my code:

    #include <stdio.h>
    
    int main(void) {
        FILE *fp;
        fp = fopen("Data", "r");
        char *str_read[3];
        int i;
        for (i = 0; i < 3; i++){
            fscanf(fp, "%s", &str_read[i]);
            printf("%s\n", str_read[i]);
        }
        fclose(fp);
        return 0;
    }

I'm using Pelles C for Windows, on Windows 11. I don't know whether there's a mistake in my code or if something is off with the program I'm using (e.g. settings, missing components).

I haven't touched C in 13 years and figuring out how to compile anything was hard enough, so I doubt I'm going to figure this out on my own.

I expect the output to be three lines of text, corresponding to the contents of the "Data" file.

The code works if I get rid of fscanf() and manually set the contents of str_read, so I'm fairly sure the issue is in line 9.

I think I'm passing variables in the correct form, but I've tried messing around with & and * prefixes in pretty much every combination I could think of, in case I misunderstood the syntax.

If I choose "execute", I get "CRT: unhandled exception (main) -- terminating *** Process returned 255 ***". "Go/debug" has the aforementioned results.

Edit:

    #include <stdio.h>
    #define BUFLEN 80

    int main(void) {
        FILE *fp;
        if ((fp = fopen("Data", "r")) == NULL) {
            perror("fopen failed");
            return 1;
        }
        char str_read[3][BUFLEN];
        for (int i=0;  i<3;  i++) {
            if (fgets(str_read[i], sizeof str_read[i], fp) == NULL) {
                perror("read failed");
                return 1;
            }
            printf("%s\r\n", str_read[i]);
        }

        fclose(fp);
        return 0;
    }

This is my current code, accounting for the answers I received. I appreciate the magnitude of my mistakes and I'm aware that there's still room for improvement, but this is more than good enough for its scope.

Getting the code to work for multiple lines (in variable numbers) with fscanf() would be nice as a way to better understand the function, but fgets() seems to work better for this purpose.

I'm marking this as solved.

Eptagon
  • 68
  • 7
  • 3
    None of the pointers in `str_read` point to *anything* properly allocated for storing data retrieved from `fscanf`. All `str_read` contains is three pointers to lord-inowws-where. Start by fixing that. I'd also address the mid-sized elephant in the code, that you're blissfully assume `fp` contains a valid file pointer rather than checking it for certainty. – WhozCraig Dec 28 '22 at 23:58
  • Did you turn on warnings? (If not, that’s the first step.) – Davis Herring Dec 28 '22 at 23:58

3 Answers3

5

There are multiple problems here:

  1. You need to check to see if fopen failed.
  2. You need to allocate your string buffer correctly.
  3. You need to ensure you don't OVERWRITE your buffer.
  4. &str_read[i] forms a char** but fscanf(fp, "%s", ... requires a char*, so:
    fscanf(fp, "%19s", str_read[i]);
    // 19 if your buffer has size 20 to not write out of bounds
    

EXAMPLE:

#include <stdio.h>

#define BUFLEN 80

int main(void) {
    FILE *fp;
    char buffer[BUFLEN];
    int i;
    if ((fp = fopen("Data", "r") == NULL) {
      perror("fopen failed");
      return 1;
    }
    
    if (fgets(buffer, BUFLEN, fp) == NULL) {
      perror("read failed");
      return 1;
    }
    
    printf("%s", buffer);
    fclose(fp);
    return 0;
}
Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
paulsm4
  • 114,292
  • 17
  • 138
  • 190
  • Thanks a lot for the input. `if ((fp = fopen("Data", "r") == NULL)` On my machine, this breaks with two errors and a warning: error #2168: Operands of '=' have incompatible types 'FILE (aka (incomplete) struct FILE) *' and 'int'. error #2001: Syntax error: expected ')' but found '{'. warning #2030: '=' used in a conditional expression. If I revert to the unchecked version, printf() gets me the first line of "Data". Should I want to keep using fscanf(), how would I allocate space for it? Would using buffer as argument 3 and setting str_read to it for each i work, for instance? – Eptagon Dec 29 '22 at 01:28
  • 1
    There’s a typo in that. Count your parentheses. It should be: `if ((fp = fopen(...)) == NULL)` – Dúthomhas Dec 29 '22 at 02:32
  • You're right, thanks. I ended up ditching fscanf(), as I don't see a way to make it work for multiple lines. I'll update the main post as soon as I'm able to and likely accept the above comment as an answer. – Eptagon Dec 29 '22 at 02:37
  • @Eptagon Please _don’t_ change the content of your question. It prevents other people with similar problems from understanding that they have a similar problem and advantaging themselves of the answers here. – Dúthomhas Dec 29 '22 at 03:23
  • 1
    @Dúthomhas I only added content afterwards, the original body of the question is still there. – Eptagon Dec 30 '22 at 12:09
0

The scanf() function never allocates space for an input string. You must do that ahead of time.

You shouldn’t be using scanf(), though. I assume you are attempting to read a line of input. Use fgets().

char s[1000];

// Attempt to read 999 characters maximum.
// Guaranteed to be nul-terminated on success.
if (!fgets(s, sizeof s, fp))
  fooey();

// Did we get the entire line?
// (Up-to and including the newline / ENTER key press?)
char * p = strpbrk(s, "\n\r");
if (!p)
  fooey();
*p = '\0'; // if yes then trim the newline

// Great! We got a complete line of text from the user.
printf("Got \"%s\"\n", s);

You may find it useful to put all the get-a-line stuff in a helpful function:

char * get_a_line(FILE *fp)
{
  static char s[1000];
  ...
  return s;
}

And use strdup() on the result.

for (int i=0;  i<3;  i++)
{
  char * s = get_a_line(fp);
  strs[i] = s ? strdup(s) : NULL;
}

Alternate version:

char * get_a_line(FILE *f)
{
  char s[1000] = {0};
  ...
  return strdup(s);
}

And:

for (int i=0;  i<3;  i++)
  strs[i] = get_a_line(fp);

Don’t forget to clean up before you are done:

for (int i=0;  i<3;  i++)
  free(strs[i]);

Of course, you could just allocate an array for your maximum expected string sizes:

char strs[3][500];

...

for (int n=0;  n<3;  n++)
{
  fgets(strs[n], sizeof strs[n], fp);
  char *p = strpbrk(strs[n], "\n\r");
  ...
}

C is much more obnoxious about getting input than you remember, right?

Dúthomhas
  • 8,200
  • 2
  • 17
  • 39
0

Now that there's an accepted answer AND the OP has posted a working revision of their code, here is an alternative version. It may be slightly easier to work with because it is slightly smaller and deals with the problem in discrete phases: Open, load, chop, print.

Just posting this in the hope it may help others.

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

int main( void ) {
    const int maxlines = 3; // change to suit requirements

    /* OPEN */
    FILE *fp = fopen( "Data", "r" );
    if( fp == NULL ) {
        perror( "fopen failed" );
        return 1;
    }

    /* LOAD */
    char buf[ maxlines * 100 + 1 ]; // Sloppy, but big enough and simple.
    size_t nread = fread( buf, 1, sizeof buf, fp );
    buf[ nread ] = '\0';
    fclose(fp);

    /* CHOP */
    char *str_read[ maxlines ] = { NULL };
    int gotLines = 0;
    for( char *cp = buf; i < maxlines && (cp = strtok( cp, "\r\n")) != NULL; cp++, gotLines++ )
        str_read[ gotLines ] = cp;

    /* PRINT */
    for( int i = 0; i < gotLines; i++ )
        printf("%s\r\n", str_read[i]);
    
    return 0;
}

Elaborating on this to use heap storage instead of stack is left as an exercise for the reader.

Fe2O3
  • 6,077
  • 2
  • 4
  • 20