CodeRigor

FREE LESSON

FILE streams, buffering & binary I/O

Use fread/fwrite counts, distinguish EOF from stream errors, and flush buffered output while keeping FILE ownership explicit.

A FILE stream is a C library object with buffering, error/EOF indicators and a logical position. It is not interchangeable with an integer Unix descriptor. Use for the standard stream interface, and keep acquisition, borrowed operations and finalization visible.

A short final read still contains data
Write all three returned bytes; the rest of the seven-byte buffer is not part of this result.
A short final read still contains data
Write all three returned bytes; the rest of the seven-byte buffer is not part of this result.

Own streams, borrow operations

fopen and tmpfile return an owned FILE pointer or nullptr. A unique_ptr with fclose as its deleter is useful for cleanup; an operation accepting FILE* usually borrows it. Never fclose a borrowed stream or duplicate ownership of the same pointer. A destructor can ensure release, but an explicit finalization step is needed if fclose failure must be reported to the caller.

Open binary files with an appropriate b mode when bytes must be preserved across platforms. Update streams have direction-change rules: output followed by input needs a flush or positioning operation; input followed by output needs positioning unless the input encountered EOF. The examples use rewind/fseek to make that boundary explicit. Do not mix raw descriptor operations and buffered stream I/O casually.

Follow one complete example

Run this program, then change one boundary while keeping the API preconditions valid. Predict its output before pressing Run.

C++
#include <cstdio>
#include <memory>
int main() {
    using file=std::unique_ptr<std::FILE,decltype(&std::fclose)>;
    file f(std::tmpfile(),std::fclose); if(!f)return 1;
    const unsigned char input[]={65,0,66};
    if(std::fwrite(input,1,3,f.get())!=3)return 2;
    if(std::fseek(f.get(),0,SEEK_SET)!=0)return 3;
    unsigned char output[8]{};auto n=std::fread(output,1,8,f.get());
    std::printf("%zu %u %d %d\n",n,unsigned(output[1]),std::feof(f.get())!=0,std::ferror(f.get())!=0);
}
A byte-copy loop has two result channels
Returned bytes and stream indicators each carry information.
A byte-copy loop has two result channels
Returned bytes and stream indicators each carry information.

Control reads by their returned count

fread reports complete elements, so fread(buffer,1,capacity,file) gives a byte count. A short result can still contain useful data. Process those bytes first, then distinguish feof from ferror. A loop controlled only by !feof predicts something the indicator does not promise and often processes stale data once more.

fgetc returns int so every byte stays distinct from EOF. fgets retains a newline when it fits and can return one long logical line in pieces; define a maximum-line policy instead of silently treating each chunk as a complete record. rewind resets position and indicators, while clearerr resets indicators without performing a transfer or repairing the underlying cause.

Library buffering and kernel handles
FILE owns library state above the host interface; these layers have different guarantees.
Library buffering and kernel handles
FILE owns library state above the host interface; these layers have different guarantees.

Buffered acceptance is not completed output

fwrite can accept bytes into a user-space buffer. A later fflush or fclose can report the actual output error. Check positive partial counts, avoid a zero-progress write loop and check the final flush when success promises flushed output. The helper does not claim rollback: bytes already transferred stay transferred on a later failure.

fflush sends the library buffer to the host environment; it is not a power-loss persistence guarantee. Portable ISO C does not define using fflush as a general input-discard operation. The companion Unix/Linux course explains descriptors and host-specific durability APIs. Here the implementation uses standard stdio, while a controlled POSIX fixture closes a backing descriptor to prove that a buffered flush failure is detected.

Read the return value

Predict this smaller case. The check item later changes the values, so explain the rule as well as the output.

C++
#include <cstdio>
int main(){auto f=std::tmpfile();if(!f)return 1;std::fputs("abc",f);std::rewind(f);char b[8]{};auto n=std::fread(b,1,8,f);std::printf("%zu %d\n",n,std::feof(f)!=0);std::fclose(f);}

Complete the faded example

State the missing precondition or result check before filling the code. The fill exercise supplies the rest of the implementation.

C++
auto n=std::fread(buffer.data(), 1, buffer.size(), source);
// Fill: write the n returned bytes completely.
// Fill: distinguish ferror from feof.
// Fill: check the destination flush before success.

Check your model

If the final fread returns three bytes and sets EOF, which happens first: writing those bytes or leaving the loop?

Practice the boundary

Implement copy_stream(FILE* source, FILE* destination) -> std::expected<std::size_t,stream_error>.

Borrow two distinct binary streams at their current positions. The source is open for reading and the destination for writing; both refer to finite files, and any update-stream direction changes have already been synchronized by the caller. Their lifetime remains the caller's responsibility. Neither is closed, rewound or cleared by this utility.

Reject null or identical pointers as invalid. A pre-existing source error returns read; a pre-existing destination error returns write. Read and write byte-sized elements until EOF, preserving embedded NULs and the final partial block. Return the total byte count on success. Check ferror separately from feof, handle positive partial writes, and report zero write progress or output error as write. Flush output at the end and report flush failure. Read errors return read; output failures encountered while writing a returned block take precedence. No rollback is promised on failure.

Total source bytes fit size_t. A successful flush transfers stdio buffers to the host environment; it does not promise power-loss durability. Implement with the C stdio interface.

Read → six checks → two bug hunts → fill → build. A successful graded submission is evidence; reading or watching alone does not grant mastery. Review cards reinforce API choices separately.

Watch and verify

The Watch & read panel links a free lecture for the relevant background and primary API references. The lecture notes say which parts are background; the tested contract here supplies the exact wrapper policy.

C standard-library course · C library review cards · Unix/Linux systems

Watch & read

Free lectures, talks and readings that cover this skill from another angle. All resources →

Focus on FILE pointers and binary file I/O. The lesson extends that model to short counts, error indicators, flush failures and explicit C++ ownership.