Exercise 1-16. Revise the main routine of the longest-line program so it will correctly print the length of arbitrarily long input lines, and as much as possible of the text.
The K&R longest-line program (Section 1.9) stores each input line in a fixed buffer of MAXLINE characters. That design silently truncates any line longer than the buffer — worse, the reported length is wrong because the original getline stops counting at the buffer boundary. The fix requires separating two concerns that the original code conflated: how many characters we store versus how many the line actually contains. The solution uses two counters inside the revised getline2: i tracks where the next character lands in the buffer (capped at MAXLINE-1), and len keeps incrementing until the newline or EOF regardless of whether the buffer is full. The caller in main receives the true length and prints it, while the buffer holds the text that fit — satisfying both requirements of the exercise.
Solution
/* Compile: gcc -ansi -Wall ex1-16.c -o ex1-16 */
#include <stdio.h>
#define MAXLINE 1000
int getline2(char line[], int maxline);
void copy(char to[], char from[]);
int main(void)
{
int len, max;
char line[MAXLINE], longest[MAXLINE];
max = 0;
while ((len = getline2(line, MAXLINE)) > 0) {
if (len > max) {
max = len;
copy(longest, line);
}
}
if (max > 0)
printf("%d: %s", max, longest);
return 0;
}
/*
* getline2: read a line into line[], return the TRUE length of the line.
* Up to maxline-1 characters are stored; the rest are counted but discarded.
*/
int getline2(char line[], int maxline)
{
int c, i, len;
len = 0; /* true character count for this line */
i = 0; /* index into the storage buffer */
while ((c = getchar()) != EOF && c != '\n') {
if (i < maxline - 1) /* buffer still has room */
line[i++] = c;
++len; /* always count, even past the buffer */
}
if (c == '\n') {
if (i < maxline - 1)
line[i++] = '\n';
++len;
}
line[i] = '\0';
return len; /* true length, not capped at maxline */
}
/* copy: copy from[] into to[]; assumes to[] is large enough */
void copy(char to[], char from[])
{
int i = 0;
while ((to[i] = from[i]) != '\0')
++i;
}
The Two-Counter Trick Explained
The key difference from the original program is inside the while loop of getline2:
i— the buffer index. The guardi < maxline - 1prevents overflow, soinever grows past the buffer boundary.len— the true line length. It is incremented unconditionally on every character, even after the buffer is full.
When the loop ends, i tells us how many characters were stored and len tells us how many characters the input line actually contained. The function returns len, so main always compares and prints the correct length. The stored text — “as much as possible” — is whatever fit inside MAXLINE-1 characters.
The function is named getline2 rather than getline to avoid a name collision with the POSIX getline() function declared in <stdio.h> on modern systems.
Compile and Run
gcc -ansi -Wall ex1-16.c -o ex1-16
./ex1-16
Type lines of input followed by Enter. Press Ctrl+D (Linux/macOS) or Ctrl+Z then Enter (Windows) to signal end-of-file. The program prints the true length and content of the longest line found.
To test with a line longer than MAXLINE, use a shell trick:
python3 -c "print('A' * 1500)" | ./ex1-16
Sample Output
Input (three lines typed): hello world the quick brown fox jumps over the lazy dog hi Output: 44: the quick brown fox jumps over the lazy dog
With the 1500-character test line (buffer is 1000):
1501: AAAA...AAA
(only the first 999 A's are printed, but the length 1501 is correct)
Compare this to the original K&R program, which would report a length of 1000 — the wrong answer — because it stopped counting at the buffer boundary.
What This Exercise Teaches
- Separating buffer position from logical length. Two counters doing different jobs inside the same loop is a fundamental C pattern that reappears in parsers, I/O routines, and network code.
- Fixed-size buffer discipline. The guard
i < maxline - 1(leaving room for'\0') is the canonical way to prevent buffer overflow without allocating dynamic memory. - Reading past a full buffer. Continuing to consume characters after the buffer is full is necessary both for correctness (true length) and to keep the input stream in a known state for the next call.
- Name collision awareness. POSIX added
getline()to<stdio.h>; renaming togetline2avoids a silent conflict that would break the build on Linux with-D_POSIX_C_SOURCEor-D_GNU_SOURCE.
Set Up Your C Environment
To compile and run this solution, you need GCC installed. If you haven’t set up C on your machine yet:
- Complete C Development Environment Setup — recommended starting point
- Install GCC on Windows 11
- Install GCC on macOS
- Install GCC on Ubuntu/Linux
- VS Code for C Programming — recommended editor
← Exercise 1-15 |
Chapter 1 Solutions |
Exercise 1-17 →
Book:
The C Programming Language, 2nd Ed — Kernighan & Ritchie