Exercise 8-3. Design and write
_flushbuf,fflush, andfclose.
Exercise 8-2 built the read half of a miniature stdio; this one builds the write half. The design mirrors getc/_fillbuf: putc is a macro that drops characters into a buffer, and only when the buffer fills does it call the real function — _flushbuf — which writes the whole buffer with one system call and stores the character that didn’t fit.
The subtle part is that _flushbuf serves three masters: the very first putc (no buffer exists yet — allocate one, write nothing), a full buffer (write it, then store the new character), and a flush request from fflush/fclose (write pending bytes, store nothing — we signal this by passing EOF as the character). fclose then completes the life cycle: flush, free the buffer, clear the flags so the slot can be reused, and close the descriptor.
Solution
/* K&R Exercise 8-3: design and write _flushbuf, fflush, and fclose.
* Compile: gcc -ansi -Wall -Wextra ex8_3.c -o ex8_3 */
#define _POSIX_C_SOURCE 200112L
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h> /* printf/fopen for the demo check only */
#define PERMS 0666
#define MY_OPEN_MAX 20
#define MYBUFSIZ 1024
#define MY_EOF (-1)
typedef struct {
int cnt; /* characters left in buffer */
char *ptr; /* next character position */
char *base; /* location of buffer */
struct {
unsigned is_read : 1;
unsigned is_write : 1;
unsigned is_unbuf : 1;
unsigned is_eof : 1;
unsigned is_err : 1;
} flag;
int fd; /* file descriptor */
} MYFILE;
static MYFILE _iob[MY_OPEN_MAX];
#define my_putc(x, p) (--(p)->cnt >= 0 \
? (unsigned char)(*(p)->ptr++ = (char)(x)) : _flushbuf((x), p))
/* _flushbuf: write out the full (or final) buffer, then store c */
static int _flushbuf(int c, MYFILE *fp)
{
int bufsize;
ssize_t nc;
if (!fp->flag.is_write || fp->flag.is_err)
return MY_EOF;
bufsize = fp->flag.is_unbuf ? 1 : MYBUFSIZ;
if (fp->base == NULL) { /* first write: no buffer yet */
if ((fp->base = malloc((size_t)bufsize)) == NULL) {
fp->flag.is_err = 1;
return MY_EOF;
}
} else { /* buffer full (or being flushed) */
nc = fp->ptr - fp->base;
if (nc > 0 && write(fp->fd, fp->base, (size_t)nc) != nc) {
fp->flag.is_err = 1;
return MY_EOF;
}
}
fp->ptr = fp->base;
if (c == MY_EOF) { /* flush-only call: store nothing */
fp->cnt = bufsize;
return 0;
}
fp->cnt = bufsize - 1; /* one slot used by c below */
*fp->ptr++ = (char)c;
return (unsigned char)c;
}
/* my_fflush: force pending buffered output to the file */
static int my_fflush(MYFILE *fp)
{
if (fp == NULL || !fp->flag.is_write)
return MY_EOF;
if (fp->base == NULL) /* nothing was ever written */
return 0;
return _flushbuf(MY_EOF, fp) == MY_EOF ? MY_EOF : 0;
}
/* my_fclose: flush, release the buffer, close the descriptor */
static int my_fclose(MYFILE *fp)
{
int rc;
if (fp == NULL)
return MY_EOF;
rc = 0;
if (fp->flag.is_write && my_fflush(fp) == MY_EOF)
rc = MY_EOF;
free(fp->base);
fp->base = fp->ptr = NULL;
fp->cnt = 0;
fp->flag.is_read = fp->flag.is_write = 0;
fp->flag.is_unbuf = fp->flag.is_eof = fp->flag.is_err = 0;
if (close(fp->fd) == -1)
rc = MY_EOF;
return rc;
}
/* my_fopen: as in Exercise 8-2 (write and append paths matter here) */
static MYFILE *my_fopen(const char *name, char mode)
{
int fd;
MYFILE *fp;
if (mode != 'r' && mode != 'w' && mode != 'a')
return NULL;
for (fp = _iob; fp < _iob + MY_OPEN_MAX; fp++)
if (!fp->flag.is_read && !fp->flag.is_write)
break;
if (fp >= _iob + MY_OPEN_MAX)
return NULL;
if (mode == 'w')
fd = creat(name, PERMS);
else if (mode == 'a') {
if ((fd = open(name, O_WRONLY, 0)) == -1)
fd = creat(name, PERMS);
lseek(fd, 0L, 2);
} else
fd = open(name, O_RDONLY, 0);
if (fd == -1)
return NULL;
fp->fd = fd;
fp->cnt = 0;
fp->base = NULL;
fp->flag.is_unbuf = fp->flag.is_eof = fp->flag.is_err = 0;
if (mode == 'r') {
fp->flag.is_read = 1;
fp->flag.is_write = 0;
} else {
fp->flag.is_read = 0;
fp->flag.is_write = 1;
}
return fp;
}
static void my_fputs(const char *s, MYFILE *fp)
{
while (*s)
my_putc(*s++, fp);
}
int main(void)
{
MYFILE *fp;
FILE *check;
int c, i;
if ((fp = my_fopen("ex8_3_out.txt", 'w')) == NULL) {
printf("my_fopen failed\n");
return 1;
}
for (i = 1; i <= 3; i++)
my_fputs("written through my_putc and _flushbuf\n", fp);
my_fputs("partial line before fflush", fp);
my_fflush(fp); /* pending bytes hit the disk now */
my_fputs(" ...and after it\n", fp);
if (my_fclose(fp) == MY_EOF) { /* final flush happens here */
printf("my_fclose failed\n");
return 1;
}
/* prove the round-trip with ordinary stdio */
if ((check = fopen("ex8_3_out.txt", "r")) == NULL)
return 1;
while ((c = getc(check)) != EOF)
putchar(c);
fclose(check);
return 0;
}
Compile and Run
gcc -ansi -Wall -Wextra ex8_3.c -o ex8_3
./ex8_3
Sample Output
written through my_putc and _flushbuf written through my_putc and _flushbuf written through my_putc and _flushbuf partial line before fflush ...and after it
Real captured run — the file written through our layer reads back byte-for-byte through ordinary stdio, including the bytes forced out mid-stream by my_fflush.
What This Exercise Teaches
- Deferred buffer allocation — the buffer appears on the first
putc, not atfopen - The sentinel pattern: passing
EOFto_flushbufto mean “flush only, store nothing” - Why
fclosemust flush before it frees — and why exiting without it loses buffered data - Resource life cycle:
malloc/freeof the buffer paired withopen/closeof the descriptor
Set Up Your C Environment
This chapter uses UNIX system calls — a Linux machine, macOS terminal, or WSL2 on Windows all work:
- Install GCC on Ubuntu/Linux
- Install GCC on Windows 11 (use the WSL2 path for this chapter)
- Install GCC on macOS
← Exercise 8-2 |
All K&R Solutions |
Exercise 8-4 →
Book:
The C Programming Language, 2nd Ed — Kernighan & Ritchie