Exercise 2-2. Write a loop equivalent to the
forloop below without using&&or||:for (i = 0; i < lim - 1 && (c = getchar()) != '\n' && c != EOF; ++i) s[i] = c;
Understanding the Problem
The && operator in C is a short-circuit operator: if the left operand evaluates to false (zero), the right operand is never evaluated at all. This matters enormously in the loop above because the second condition, (c = getchar()), is not a pure test — it reads a character from standard input as a side effect.
The three conditions in the original for loop are evaluated strictly left to right, and evaluation stops the moment one fails:
i < lim - 1— if the buffer is full, stop immediately. Do not callgetchar().(c = getchar()) != '\n'— only if (1) passed: read one character, stop if it is a newline.c != EOF— only if (2) passed: stop if end-of-file was reached.
To replicate this without &&, you need the same sequence: check each condition in order, and exit the loop immediately when one fails — using break. The trap to avoid is calling getchar() before checking the buffer limit; doing so would consume a character from the input stream that should never have been read.
Solution — Using break (primary)
/* Compile: gcc -ansi -Wall exercise2-2.c -o exercise2-2 */
#include <stdio.h>
#define MAXLINE 1000
int getline2(char s[], int lim);
int main(void)
{
char line[MAXLINE];
int len;
while ((len = getline2(line, MAXLINE)) > 0)
printf("%s", line);
return 0;
}
int getline2(char s[], int lim)
{
int c, i;
i = 0;
while (1) {
if (i >= lim - 1) /* buffer full: stop, do NOT call getchar() */
break;
c = getchar(); /* only reached when there is still room */
if (c == EOF)
break;
if (c == '\n')
break;
s[i++] = c;
}
if (c == '\n') /* preserve the newline in the stored line */
s[i++] = '\n';
s[i] = '\0';
return i;
}
Each if … break pair corresponds exactly to one condition in the original for. Because break exits the loop the moment it fires, later checks are skipped — the same effect as short-circuit evaluation. In particular, getchar() is only ever called after confirming that the buffer has room, exactly mirroring the original.
Alternative Solution — Using a Flag Variable
Another approach replaces the short-circuit with an explicit done flag. It is more verbose, but makes the control flow visible without any break statements:
int getline2_flag(char s[], int lim)
{
int c = 0, i = 0, done = 0;
while (done == 0) {
if (i >= lim - 1) {
done = 1; /* buffer full */
} else {
c = getchar();
if (c == EOF)
done = 1;
else if (c == '\n')
done = 1;
else
s[i++] = c;
}
}
if (c == '\n')
s[i++] = '\n';
s[i] = '\0';
return i;
}
The flag version works correctly, but it requires an extra variable and more indentation. Notice that c is initialized to 0 here — this makes the final if (c == '\n') safe even if getchar() was never called (which happens when lim is 1). The break version shares the same subtle edge case; in practice it never occurs because lim is always MAXLINE (1000). The break approach is the cleaner, more idiomatic C solution.
Why the Order of Checks Matters
Consider a student who writes this instead:
/* WRONG — getchar() is called even when the buffer is full */
while (1) {
c = getchar();
if (i >= lim - 1) break;
if (c == EOF) break;
if (c == '\n') break;
s[i++] = c;
}
This silently discards one character every time the buffer limit terminates the loop. The original for loop would not have called getchar() in that case — the short-circuit stopped evaluation at condition 1. Faithfully replicating short-circuit behaviour means preserving the order and the guards.
Compile and Run
gcc -ansi -Wall exercise2-2.c -o exercise2-2
./exercise2-2
The program reads lines from standard input and echoes them back. Type text and press Enter, or pipe input in:
printf "Hello, world!\nSecond line\n" | ./exercise2-2
Sample Output
$ printf "Hello, world!\nSecond line\n" | ./exercise2-2 Hello, world! Second line $ printf "abc" | ./exercise2-2 abc
The second example shows end-of-file without a trailing newline — getchar() returns EOF, the loop breaks, and the string is stored without appending '\n'.
What This Exercise Teaches
- Short-circuit evaluation:
&&and||do not evaluate their right operand if the result is already determined by the left. This is a guarantee in C, not an optimisation hint. - Side effects in conditions:
(c = getchar())inside a condition both reads a character and produces a value — code that relies on short-circuit behaviour depends on the left operand being tested first. - Replicating short-circuit with
break: The equivalence holds only if each guard is checked in the same order as the original conditions, and later guards are skipped when an earlier one fires. - Trade-offs between
breakand flag variables: Both are correct;breakis more concise and direct, while a flag makes state transitions explicit — understanding both prepares you for real-world code review.
Set Up Your C Environment
To compile and run this solution, you need GCC installed. If you have not set up C on your machine yet:
- Install GCC on Windows 11
- Install GCC on macOS
- Install GCC on Ubuntu/Linux
- VS Code for C Programming — recommended editor
← Exercise 2-1 |
Chapter 2 Solutions |
Exercise 2-3 →
Book:
The C Programming Language, 2nd Ed — Kernighan & Ritchie