12. File I/O: buffering, binary vs. text mode, errno
12.1 stdio buffering — why your output sometimes "doesn't show up"
#include <stdio.h>
#include <unistd.h>
int main(void) {
printf("about to crash..."); // stdout is line-buffered (tty) or fully buffered (pipe/file) —
// this may sit in a buffer, unflushed, if stdout isn't a tty
fflush(stdout); // force it out now
// *(int *)0 = 1; // simulate a crash — without the fflush above,
// the message might never appear
return 0;
}
stderr is unbuffered by default specifically so error messages appear
immediately even if the process crashes right after — this is why
diagnostics go to stderr, not stdout.
12.2 Reading a whole file, correctly
#include <stdio.h>
#include <stdlib.h>
char *read_whole_file(const char *path, size_t *out_len) {
FILE *f = fopen(path, "rb"); // "b" matters: on Windows, text mode translates
if (!f) return NULL; // \r\n <-> \n, corrupting binary data
fseek(f, 0, SEEK_END);
long size = ftell(f);
if (size < 0) { fclose(f); return NULL; }
rewind(f);
char *buf = malloc((size_t)size + 1);
if (!buf) { fclose(f); return NULL; }
size_t read = fread(buf, 1, (size_t)size, f);
fclose(f);
if (read != (size_t)size) { free(buf); return NULL; }
buf[size] = '\0';
if (out_len) *out_len = read;
return buf; // caller owns this — must free()
}
12.3 errno: check it right after the call that can fail, nothing else
#include <errno.h>
#include <stdio.h>
#include <string.h>
int main(void) {
FILE *f = fopen("/does/not/exist", "r");
if (!f) {
// errno is set by the failing call; a call in between (even printf) can clobber it
fprintf(stderr, "fopen failed: %s\n", strerror(errno));
return 1;
}
fclose(f);
return 0;
}
perror("fopen") is the shorthand for the same thing:
perror prints "fopen: No such file or directory\n" using the current
errno automatically.