I/O#
C has two I/O surfaces: the stdio layer in
<stdio.h> (fopen, fread, fwrite, printf)
and the POSIX syscall layer in <unistd.h> (open,
read, write). The stdio layer is buffered and portable;
the syscall layer is unbuffered and Unix-specific.
For networking I/O, see Networking. For CLI argument plumbing, see CLI.
Buffered (stdio)#
Files#
#include <stdio.h>
FILE *f = fopen(path, "rb"); /* "r" "w" "a" + "b" for binary */
if (!f) { perror("fopen"); return -1; }
char buf[4096];
size_t n;
while ((n = fread(buf, 1, sizeof buf, f)) > 0) {
process(buf, n);
}
if (ferror(f)) perror("fread");
fclose(f);
FILE *out = fopen("out.txt", "w");
fputs("hello\n", out);
fprintf(out, "port=%d\n", 8080);
fclose(out);
Standard streams#
stdin, stdout, stderr are pre-opened FILE *.
fputs("hello, world\n", stdout);
fprintf(stderr, "warning: %s\n", msg);
char line[1024];
while (fgets(line, sizeof line, stdin)) {
/* line includes the trailing \n unless EOF cut it short */
}
Use fgets (or getline on POSIX), never
gets (removed in C11, with cause).
Formatted I/O#
printf family. Common verbs.
Verb |
Type |
Example |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
the right verb for sizes |
|
hex |
|
|
|
|
|
|
|
|
|
|
|
pointer |
|
|
literal |
For stdint.h types, <inttypes.h> defines macros.
#include <inttypes.h>
uint64_t v = 1;
printf("%" PRIu64 "\n", v);
snprintf writes to a buffer with a size cap; the operator
uses it everywhere instead of sprintf (which can overflow).
char msg[64];
int n = snprintf(msg, sizeof msg, "%s:%d", host, port);
if (n < 0 || (size_t)n >= sizeof msg) { /* truncated */ }
Unbuffered (POSIX)#
The syscall layer talks to the kernel directly. No buffering;
each read / write is a syscall.
#include <fcntl.h>
#include <unistd.h>
int fd = open(path, O_RDONLY);
if (fd < 0) { perror("open"); return -1; }
char buf[4096];
ssize_t n;
while ((n = read(fd, buf, sizeof buf)) > 0) {
process(buf, n);
}
if (n < 0) perror("read");
close(fd);
For writes, write returns the number of bytes actually
written; the operator loops if it is less than requested.
ssize_t written = 0;
while ((size_t)written < n) {
ssize_t w = write(fd, buf + written, n - written);
if (w < 0) { if (errno == EINTR) continue; return -1; }
written += w;
}
Memory-mapped I/O#
mmap maps a file into the process’s address space. Useful
for large read-only inputs (search through gigabyte files
without copying through buffers).
#include <sys/mman.h>
int fd = open(path, O_RDONLY);
struct stat st; fstat(fd, &st);
void *base = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (base == MAP_FAILED) { perror("mmap"); return -1; }
/* base[0..st.st_size] is the file contents */
munmap(base, st.st_size);
close(fd);
Reading whole files#
The “read a file into memory” idiom.
int slurp(const char *path, char **out, size_t *out_n) {
FILE *f = fopen(path, "rb");
if (!f) return -1;
fseek(f, 0, SEEK_END);
long n = ftell(f);
fseek(f, 0, SEEK_SET);
char *buf = malloc((size_t)n + 1);
if (!buf) { fclose(f); return -1; }
size_t got = fread(buf, 1, (size_t)n, f);
fclose(f);
if (got != (size_t)n) { free(buf); return -1; }
buf[n] = '\0';
*out = buf;
*out_n = (size_t)n;
return 0;
}
Atomic writes#
Write to a temp file, fsync, then rename over the
target.
int write_atomic(const char *path, const void *data, size_t n) {
char tmp[PATH_MAX];
snprintf(tmp, sizeof tmp, "%s.tmp", path);
int fd = open(tmp, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd < 0) return -1;
const char *p = data;
while (n > 0) {
ssize_t w = write(fd, p, n);
if (w < 0) { close(fd); unlink(tmp); return -1; }
p += w; n -= w;
}
fsync(fd);
close(fd);
if (rename(tmp, path) < 0) { unlink(tmp); return -1; }
return 0;
}
References#
Types for
size_t/ssize_t/FILE *.Errors for
errno/perrorafter I/O calls.Networking for socket I/O.