K&R C Exercise 8-2: fopen and _fillbuf with Bit-Fields

Exercise 8-2. Rewrite fopen and _fillbuf with fields instead of explicit bit operations. Compare code size and execution speed.

Chapter 8 sketches a miniature version of the standard I/O library: a FILE structure holding a buffer, a count, a position pointer, and a set of status flags. In the book the flags live in one int and are tested with bit masks like fp->flag & _READ. The exercise asks for the same machinery expressed with bit-fields — named one-bit members the compiler packs for you. The logic of fopen and _fillbuf is untouched; what changes is that every masked test becomes a readable member access: fp->flag.is_read instead of fp->flag & _READ, and clearing a flag no longer needs a carefully inverted mask.

The one real trap: with masks, “this slot is free” is written (fp->flag & (_READ | _WRITE)) == 0. With fields it must become !fp->flag.is_read && !fp->flag.is_write — there is no way to test two bit-fields in one operation, which is exactly the code-size trade-off the exercise wants you to notice.

Solution

/* K&R Exercise 8-2: fopen and _fillbuf with bit-fields instead of
 * explicit bit operations.
 * Compile: gcc -ansi -Wall -Wextra ex8_2.c -o ex8_2 */
#define _POSIX_C_SOURCE 200112L
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>          /* printf for the demo only */

#define PERMS       0666
#define MY_OPEN_MAX 20      /* max files open at once */
#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 {                /* the exercise: fields, not bit masks */
        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_getc(p) (--(p)->cnt >= 0 \
    ? (unsigned char) *(p)->ptr++ : _fillbuf(p))

/* _fillbuf: allocate and fill input buffer */
static int _fillbuf(MYFILE *fp)
{
    int bufsize;
    ssize_t n;

    if (!fp->flag.is_read || fp->flag.is_eof || fp->flag.is_err)
        return MY_EOF;
    bufsize = fp->flag.is_unbuf ? 1 : MYBUFSIZ;
    if (fp->base == NULL)               /* no buffer yet */
        if ((fp->base = malloc((size_t)bufsize)) == NULL)
            return MY_EOF;              /* can't get buffer */
    fp->ptr = fp->base;
    n = read(fp->fd, fp->ptr, (size_t)bufsize);
    fp->cnt = (int)n;
    if (--fp->cnt < 0) {
        if (fp->cnt == -1)
            fp->flag.is_eof = 1;        /* read returned 0 */
        else
            fp->flag.is_err = 1;        /* read returned < 0 */
        fp->cnt = 0;
        return MY_EOF;
    }
    return (unsigned char) *fp->ptr++;
}

/* my_fopen: open file, return file ptr */
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;                      /* found a free slot */
    if (fp >= _iob + MY_OPEN_MAX)       /* no free slots */
        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)                       /* couldn't access name */
        return NULL;

    fp->fd = fd;
    fp->cnt = 0;
    fp->base = NULL;
    fp->flag.is_unbuf = 0;
    fp->flag.is_eof = 0;
    fp->flag.is_err = 0;
    if (mode == 'r')
        fp->flag.is_read = 1;
    else
        fp->flag.is_write = 1;
    return fp;
}

int main(void)
{
    MYFILE *fp;
    int c, count;

    /* create a small test file with ordinary stdio */
    FILE *out = fopen("ex8_2_test.txt", "w");
    if (out == NULL)
        return 1;
    fputs("bit-fields replace bit masks\n", out);
    fclose(out);

    /* read it back through OUR layer */
    if ((fp = my_fopen("ex8_2_test.txt", 'r')) == NULL) {
        printf("my_fopen failed\n");
        return 1;
    }
    count = 0;
    while ((c = my_getc(fp)) != MY_EOF) {
        putchar(c);
        count++;
    }
    printf("read %d characters through my_getc/_fillbuf\n", count);
    printf("sizeof(MYFILE) with bit-fields: %lu bytes\n",
           (unsigned long) sizeof(MYFILE));
    return 0;
}

Compile and Run

gcc -ansi -Wall -Wextra ex8_2.c -o ex8_2
./ex8_2

Sample Output

bit-fields replace bit masks
read 29 characters through my_getc/_fillbuf
sizeof(MYFILE) with bit-fields: 32 bytes

Real captured run of the exact code above (gcc on Linux x86-64).

Code Size and Speed — the Comparison the Exercise Asks For

  • Struct size: identical. The five one-bit fields pack into a single padded word — 32 bytes for MYFILE either way on x86-64, because an int flag word occupies the same aligned slot.
  • Speed: no measurable difference. The compiler implements each bit-field access with the same AND/OR masking instructions the book’s version writes by hand. Bit-fields buy readability, not performance.
  • Code size: fields can be slightly larger where masks combined tests — the free-slot check needs two field tests where flag & (_READ | _WRITE) was one instruction’s worth of masking.

What This Exercise Teaches

  • Bit-field struct members — declaring, setting, and testing 1-bit flags
  • How the standard library’s FILE structure actually works: buffer, count, position, flags, descriptor
  • The getc buffering trick: a macro that only calls a function when the buffer empties
  • Why read() returning 0 means EOF but negative means error — and keeping the two flags distinct

Set Up Your C Environment

This chapter uses UNIX system calls — a Linux machine, macOS terminal, or WSL2 on Windows all work:

← Exercise 8-1  | 
All K&R Solutions  | 
Exercise 8-3 →

Book:

The C Programming Language, 2nd Ed — Kernighan & Ritchie

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>