简体   繁体   中英

fscanf: is error or EOF possible without EOF returned?

I have a C++ program that reads from a file that I expect to have a bunch of identically formatted records. I want to stop reading if I encounter something unexpected whether that means an ill-formatted record or some input failure and I want to differentiate between these different conditions.

I've seen this answer and looked at the fscanf() documentation and I'm not sure if fscanf() can indicate an error or EOF without returning EOF. From my understanding of both those links it's possible that an error occurs or EOF occurs even if fscanf() returns a value of 0 or greater so I would have to call ferror() and feof() regardless of what value fscanf() returns. I can't seem to find how a return value of EOF is of any use to the caller.

Say I expect my file to have a bunch of records with 4 values. Would the code below properly handle any end of file and input error conditions?

  int ret;
  int field1;
  int field2;
  int field3;
  int field4;
  while ((ret = fscanf(pFile, "%d %d %d %d", &field1, &field2, &field3,
                       &field4)) == 4) {
    // do whatever with fields
  }
  if (ferror(fp)) {
    // some input error occurred
  } else if (feof(fp)) {
    // end of file occurred
  } else {
    assert(ret != EOF);
    // encountered record that didn't match expected format
  }

Update: So I'm going to add the documentation from cppreference because it seems to differ slightly in describing what condition no longer causes EOF to be returned.

Is error or EOF possible without EOF returned?

Yes it is. You could also get a return value between 0 and 3. cplusplus.com is somewhat notoriously sloppy. Let's take a look at cppreference.com's page instead.

Return value: Number of receiving arguments successfully assigned (which may be zero in case a matching failure occurred before the first receiving argument was assigned), or EOF if input failure occurs before the first receiving argument was assigned.

There are a couple different scenarios. Let's break it down into cases:

  1. If it has successfully assigned to the first receiving argument then you'll get a positive value, guaranteed. Let's say it's assigned to two variables and then hits EOF. It'll return 2 and feof() will return true.

  2. Otherwise, if it hasn't assigned to the first receiving argument and gets a matching failure it'll return 0 . What's a matching failure? That's when it's matching a specifier like %d and doesn't get a valid integer. If the input is foobar then %d will fail to match.

    Or, less commonly, it's when it's looking for a literal character and doesn't see it. For instance if your format string expected each number to be enclosed in brackets ( "[%d] [%d] [%d] [%d]" ) then it would return 0 if the input didn't start with [ .

  3. Otherwise, if it hasn't assigned to the first receiving argument and gets a EOF or a read error it'll return EOF . Note that a read error is different from a matching error. A read error is when the OS returns an I/O error trying to read from disk.

    A return value of EOF signals either end-of-file or I/O error. If you don't care about the distinction you could just abandon your loop and move on with the program. If, however, you want to print an error message on ferror() and treat feof() as success then checking ret isn't sufficient; you have to call one or both of those functions. It's up to you if you want to do that or not.

Say I expect my file to have a bunch of records with 4 values. Would the code below properly handle any end of file and input error conditions?

Yep. Looks good to me.


For what it's worth, I recommend against using scanf() and fscanf() . They're complicated and make handling input errors robustly more difficult than necessary. This question is a great demonstration. It's better to use fgets() to read an entire line and sscanf() to parse it. That way if there's bad input you don't have a partial line sitting around jamming up future reads.

I find it easier to explain the return value of the fscanf family of functions with examples.

// The string contains valid data given the format specifier.
// Expect ret to be 1.
int ret = sscanf("10", "%d", &n);

// The string contains data but not what the user expects to see.
// Expect ret to be 0.
int ret = sscanf("abcd", "%d", &n);

// The string contains no data .
// Expect ret to be EOF.
int ret = sscanf("", "%d", &n);

In all these cases, you can expect the same behavior if you have leading spaces.

// Expect ret to be 1.
int ret = sscanf("  10", "%d", &n);

// Expect ret to be 0.
int ret = sscanf("  abcd", "%d", &n);

// Expect ret to be EOF.
int ret = sscanf("  ", "%d", &n);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM