Exercise 5-13. Write the program
tail, which prints the lastnlines of its input. By default,nis 10, say, but it can be changed by an optional argument, so thattail -nprints the lastnlines. The program should behave rationally no matter how unreasonable the input or the value ofn.
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 buffer —
lines[nread % n]wraps naturally; modular indexing replaces explicit wrap-around code - “Unreasonable input” robustness —
n <= 0clamped to 1;n > MAXLINESclamped to MAXLINES; fewer lines thannhandled by capping the print count - Print order recovery — after a full circular buffer,
nread % nis the index of the oldest entry; printnentries 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