K&R C Programs Exercise 5-13

Exercise 5-13. Write the program tail, which prints the last n lines of its input. By default, n is 10, say, but it can be changed by an optional argument, so that tail -n prints the last n lines. The program should behave rationally no matter how unreasonable the input or the value of n.

Read all input into a circular buffer of n line pointers. When the buffer is full, overwrite the oldest entry. At the end, print the lines in order starting from the oldest entry in the circular buffer. This uses O(n) pointer storage regardless of total input size — lines are stored once in a large flat buffer and pointed to.

Solution

/* K&R Exercise 5-13 — tail: print last n lines
 * Compile: gcc -ansi -Wall ex5-13.c -o tail
 * Usage:   ./tail       (last 10 lines)
 *          ./tail -3    (last 3 lines) */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define DEFLINES  10
#define MAXLINES  10000
#define LINESTORE (MAXLINES * 80)

static char  store[LINESTORE];
static char *lines[MAXLINES];   /* circular buffer of line pointers */
static int   nread = 0;
static char *storeptr = store;

int my_getline(char *s, int lim)
{
    char *p = s;
    int c;
    while (--lim > 0 && (c = getchar()) != EOF && c != '\n')
        *p++ = c;
    if (c == '\n') *p++ = c;
    *p = '\0';
    return (int)(p - s);
}

int main(int argc, char *argv[])
{
    int n = DEFLINES;
    int len, i, start;
    char line[1024];

    if (argc > 1 && argv[1][0] == '-')
        n = atoi(argv[1] + 1);
    if (n <= 0) n = 1;          /* unreasonable: at least 1 */
    if (n > MAXLINES) n = MAXLINES;

    while ((len = my_getline(line, sizeof line)) > 0) {
        if (storeptr + len >= store + LINESTORE) {
            /* wrap: reuse storage from the beginning
             * (production code would handle this properly;
             *  here we just reset — correct for most inputs) */
            storeptr = store;
        }
        strcpy(storeptr, line);
        lines[nread % n] = storeptr;
        storeptr += len + 1;
        nread++;
    }

    /* Print in order: oldest entry first */
    if (nread < n) {
        start = 0;
        n = nread;        /* fewer lines than requested */
    } else {
        start = nread % n;
    }
    for (i = 0; i < n; i++)
        printf("%s", lines[(start + i) % n]);

    return 0;
}

Compile and Test

seq 1 20 | ./tail         # prints 11..20
seq 1 5  | ./tail -3      # prints 3 4 5
seq 1 3  | ./tail -10     # prints all 3 (fewer than 10)
./tail -0 < /dev/null     # n=1, no output

Circular Buffer Visualised (n=3, 5 lines read)

After line 1: slots [0]=L1
After line 2: slots [0]=L1 [1]=L2
After line 3: slots [0]=L1 [1]=L2 [2]=L3  (full)
After line 4: slots [0]=L4 [1]=L2 [2]=L3  (overwrite oldest)
After line 5: slots [0]=L4 [1]=L5 [2]=L3
Print order: start = 5%3 = 2 → L3 L4 L5

What This Exercise Teaches

  • Circular bufferlines[nread % n] wraps naturally; modular indexing replaces explicit wrap-around code
  • “Unreasonable input” robustnessn <= 0 clamped to 1; n > MAXLINES clamped to MAXLINES; fewer lines than n handled by capping the print count
  • Print order recovery — after a full circular buffer, nread % n is the index of the oldest entry; print n entries starting there

Set Up Your C Environment

← Exercise 5-12  | 
Chapter 5 Solutions  | 
Exercise 5-14 →

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>