0

In C I am trying to read/write to a file with fread/fwrite().

According to this answer i can use ferror() and feof() to check for errors.

My question is, how can I use them to do this?

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256

2 Answers2

3

The basic recipe is

size_t r = fread(buf, m, n, fp);
if(r > 0)
    success;
else if(ferror(fp))
    handle error condition;
else
    handle EOF condition;

Basically, in this case, a 0 return from fread means "either EOF or error", and you have to do something else to figure out which. The "something else" is that feof(fp) is true if there was an EOF, and ferror(fp) is true if there was an error. (Whether it's possible to have both an EOF and error condition at the same time is an interesting question which I am not going to try to answer.)

But, it's true, having both feof() and ferror() available is, mildly, overkill. Typically you only need one or the other. In the code fragment above I used ferror but not feof. Obviously I could have arranged it a different way:

if(r > 0)
    success;
else if(feof(fp))
    handle EOF condition;
else
    handle error condition;

The other thing to remember, as @chux dicusses in his answer (and I got confused on in a previous version of this answer) is that fread can return less than you asked for. So it's not an error for fread to return a value less than n, but greater than 0 — and this indicates that you've probably hit end-of-file (and feof(fp) would be true).

Things are a little different for fwrite, which returns n on success or 0 on total failure. Theoretically, fwrite can return a number greater than 0 but less than n, and this would indicate, I guess, that it successfully wrote some number of items before it hit an error, but I don't know if that case ever actually happens in practice.

Steve Summit
  • 45,437
  • 7
  • 70
  • 103
  • I'd use `if (r == n)` for success, since a short read might be indicative of an error of some kind. – Shawn Aug 05 '22 at 15:46
  • @Shawn If you've set things up correctly, 0 < `r` < `n` is always a possibility but rarely if ever an error. If you're reading, say, characters from a text file (with `m = 1`), you'll usually get a partial read at end-of-file. If you're reading multibyte records, a partial read would indeed be erroneous, but in that case you should be using a value of `m` greater than 0, and matching your record size, meaning that you can read one or more full records, but still never a partial one. – Steve Summit Aug 05 '22 at 15:51
  • Consider reading multiple multi-byte records in one go and having a partial one at the end of the file. You're expecting to read m records but get m-1. Application level error if not an actual low level input error, but still something that needs to be addressed. – Shawn Aug 05 '22 at 15:56
  • @Shawn I do consider that case, but I honestly don't know how best to address it. You may be expecting multi-byte records but not knowing how many to expect. If `m > 1`, it's difficult or impossible to know if there was a partial record. If you're reading multibyte records, and calling `fread` with `m > 1`, I doubt there's a good way to detect partial records. If you're reading multibyte records with `m = 1`, you can detect the partial records, but (a) it strikes me as not being the way `fread` was intended to be used, and (b) the success test might well be `r % recsize == 0`, not `r == n`. – Steve Summit Aug 05 '22 at 16:03
  • Hi this was a few days ago but i have one last question, when you said ```it's true, having both feof() and ferror() available is, mildly, overkill. Typically you only need one or the other``` why is this? why is it an overkill to have both? would it suffice to have only ```ferror```? – programme3219873 Aug 08 '22 at 17:34
  • 1
    @programme3219873 That sentence didn't mean much, actually. *If* it were true that `fread` could fail for exactly one of two reasons, then theoretically you wouldn't need two functions to tell which was which, you could just have one, like maybe just `ferror()`, and then if `ferror` returned false, you'd know that the "problem" was that you'd hit EOF. But, in fact, it's more complicated than that, because it's possible for both `feof()` and `ferror()` to return true at the same time, and conceivably you might want to detect and handle that case specially. – Steve Summit Aug 08 '22 at 17:39
  • 1
    In any case, redundancy isn't necessarily bad: for instance, C bas both `for` and `while` loops, even though theoretically you'd only need one, since you can always simulate one in terms of the other. – Steve Summit Aug 08 '22 at 17:39
1

@Steve Summit well covers the common cases with code like:

if (r > 0)
    success;
else if (feof(fp))
    handle EOF condition;
else
    handle error condition;

But we could dig deeper into:

size_t fread(void * restrict ptr, size_t size, size_t nmemb, FILE * restrict stream);
size_t fwrite(const void * restrict ptr, size_t size, size_t nmemb, FILE * restrict stream);
  1. fread()/fwrite() returns 0 in 4 cases:
    1.1. An end-of-file just occurred. (fread() only)
    1.2. An end-of-file had occurred prior. (fread() only)
    1.3. A read/write error just occurred (e.g. reading a file opened in write only mode or visa-versa, a read parity error, etc.)
    1.4. size or nmemb is zero.

  2. Prior to the fread()/fwrite() call, the stream, which has 2 internal flags: end-of-file and error indicators, zero, either one or both of these may be set and are reflected in feof() and ferror(). Commonly these are both clear before the fread()/fwrite() call, but not always.

  3. Sometimes the amount of data desired to be read/written is less than requested.

Putting this together for fread()/fwrite()

size_t count = fread(ptr, size, nmemb, stream); // or fwrite
if (count == 0) {
  if (feof(stream)) {
    puts("End-of-file.");
  } else if (ferror(stream)) {
    puts("Stream error.");
  } else {
    puts("Request size 0.");  // Case not expected if size, nmemb are both > 0.
  }
} else if (count < nmemb) {
  puts("Less than expected data read/written.");
} else {
  puts("Success.");
}

There is value in checking feof() before ferror() as when the error indicator is set and fread() returns 0 due to end-of-file, the correct failure reason is reported. Note that fread()/fwrite() do not return 0 if the error indicator is set. They will return 0 if an error is just then detected and then set the error indicator.

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • I disagree that you should check `feof` before `ferror`. The only reason for both `feof` and `ferror` to return true at the same time, and order matter, is if end of file is reached and a previous read had an uncaught error. In that case, by checking `ferror` first you will lose the knowledge that eof was reached, but you will have the chance to handle the error. A silent error is in my opinion *always* worse than a silent eof. – HAL9000 Aug 05 '22 at 23:10
  • @HAL9000 Your assertion is certainly at least reasonable - to report a prior error over the current end-of-file situation owing to its supposed graver concern - even though I disagree, I find it insightful. I do not see as idiomatic C. If coder does not clear clear/handle the error prior to the I/O call, code should trust the coder to be doing what the coder wants and not out-guess the coder's goal. [Trust the programmer](https://beza1e1.tuxen.de/articles/spirit_of_c.html). IAC, testing issues such as these is challenging in C as creating a sporadic I/O error lacks a C specified approach. – chux - Reinstate Monica Aug 06 '22 at 06:12
  • Yes, I agree in the idea of "Trust the programmer". If you do that, and trust that the programmer has handled all previous io-errors, then testing `feof` rather than `ferror` is just a matter of style. To summarize: Doing it one way or the other, *only* matters if there is a bug in the program, and in that case, signaling any error is better than signaling successful input processing. Especially since calling `ferror` rather than `feof` has virtually zero cost, both in runtime and programmer time (or any other metric you may think of). – HAL9000 Aug 06 '22 at 19:55
  • "...or any other metric you may think of" -- Ok, maybe not in internet discussion time :-) – HAL9000 Aug 06 '22 at 19:57
  • 1
    So, I have thought a little bit more.. What you are trying to say is that "If 'ferror` has been set before calling `fread`, that has a purpose, and don't try to question the programmers intentions, just do what you are told." Right? I can understand that kind of thought. Programs that try to act to smart are always a pain. I just don't think it is relevant in this case.. – HAL9000 Aug 06 '22 at 20:29
  • @HAL9000 Yes, my concern is more of the [that has a purpose, and don't try to question the programmers intentions](https://stackoverflow.com/questions/73250385/how-to-detect-read-write-errors-when-using-fread-and-fwrite/73251064?noredirect=1#comment129386730_73251064) and so the current reading should report about the present case and not history. – chux - Reinstate Monica Aug 06 '22 at 20:49
  • @HAL9000 Note that `fread()`, `fgets()`, `fgetc()`, etc. do not return `EOF` if the stream error indicator is set prior to performing an input attempt. I am following that standard library model. – chux - Reinstate Monica Aug 06 '22 at 20:52
  • Just to clarify why I think it is permissible to be smart in this case (So we can agree to disagree): Yes, "do what you are told" and "follow conventions" should both be holy mantras in c. But c has also the concept of undefined behaviour: If the programmer has broken or is about to break a contract, the implementation has the permission to do whatever it wants, even signal historic errors. Having the error bit set on a stream when passing it to a function is contract breakage in my opinion (at least I am going to document that in my functions from now on). `clearerr` exist for a reason. – HAL9000 Aug 06 '22 at 21:55
  • @HAL9000 "Having the error bit set on a stream when passing it to a function is contract breakage" --> I do not see that as UB for the C std lib. IAC, these are generally rare and hard to test cases - so documentation is good as to code's intent. – chux - Reinstate Monica Aug 06 '22 at 22:27
  • I don't claim that having error bit set when calling C std lib is contract breakage, just that I think it is reasonable to treat it that way in *my* functions. The reason is that after an IO-error the buffer is in an inconsistent state, and if another library function hasn't called `clearerr`, it is safe to assume that it hasn't fixed the buffer. And I am sure that reading with inconsistent buffers is some sort of contract breakage. I think APIs should be designed in such a way that any programming error is likely to trigger an abort down the line when not to much at odds with old conventions. – HAL9000 Aug 06 '22 at 23:04