C / CONSOLE INPUT AND OUTPUT
getchar, EOF and reading a stream character by character
Read a stream one byte at a time with getchar, detect end of input with EOF, and know why the character must be stored in an int, not a char.
What you will learn
- Write the canonical loop: int c; while ((c = getchar()) != EOF)
- Explain why both signed char and unsigned char break EOF detection
- Tell a real end of input from a read error using feof and ferror
- Use ungetc to push one byte back and get single-character lookahead
Understanding getchar, EOF and reading a stream character by character
stdin is a stream: an ordered sequence of bytes with a read position. getchar takes the byte at that position, moves the position forward by one, and returns the byte's value as an int between 0 and UCHAR_MAX (255 on ordinary machines). Nothing is skipped or interpreted, so spaces, tabs and the newline produced by pressing Enter come back exactly like letters do. That is the opposite of scanf, which consumes a variable, format-driven amount of input and leaves the rest behind.
The return type is int rather than char because getchar must report 257 distinct outcomes: any of the 256 possible byte values, plus "there is nothing left". EOF is a negative int, so it cannot collide with a byte value once that byte has been widened to int. Squeeze the result into a char and exactly one of two collisions happens: if char is unsigned, EOF turns into 255 and the end test never fires; if char is signed, the data byte 0xFF turns into -1 and the loop stops on legitimate input. Plain char is signed on x86 and unsigned on most ARM builds, so each bug hides on the machine where the other one shows up.
EOF is not a character stored at the end of the input; it is what getchar returns when it could not produce a character at all. Two different situations cause it, the input running out and a read error, and only feof(stdin) and ferror(stdin) distinguish them after the loop has finished. When input comes from a terminal it is line buffered, so getchar blocks until Enter is pressed and then delivers the whole line byte by byte ending with '\n'; the end of input arrives only from Ctrl-D at the start of a line (Ctrl-Z then Enter on Windows) or from a file or pipe that runs dry.
<stdio.h>
/* run it as: printf 'ab\nc\n' | ./demo */
int main(void)
{
int c;
long chars = 0, lines = 0;
while ((c = getchar()) != EOF) {
chars++;
if (c == '\n')
lines++;
printf("read %3d %c\n", c, c == '\n' ? '.' : c);
}
printf("getchar returned EOF, which is %d\n", EOF);
printf("%ld characters, %ld lines\n", chars, lines);
return 0;
}
getchar returns an int because all 256 byte values plus the "nothing left" signal must stay distinguishable, so EOF is deliberately a value that no character can have.
Worked examples
EOF does not fit in a char
Shows the two ways a char-sized variable destroys the difference between a data byte and EOF.
<stdio.h>
int main(void)
{
signed char s = EOF; /* -1 fits, but so does the data byte 0xFF */
unsigned char u = EOF; /* -1 wraps around to 255 */
printf("EOF = %d\n", EOF);
printf("EOF in a signed char = %d\n", s);
printf("EOF in an unsigned char = %d\n", u);
printf("byte 0xFF as signed char = %d\n", (signed char)0xFF);
printf("s == EOF is %s, u == EOF is %s\n",
s == EOF ? "true" : "false",
u == EOF ? "true" : "false");
return 0;
}
Example explained
Line 1unsigned char u = EOF keeps only the low 8 bits of -1, giving 255, so u == EOF can never be true and a read loop would spin forever.
Line 2(signed char)0xFF is -1, the same value as EOF, so a single 0xFF byte in the data would end that loop as if the input had finished.
Line 3An int holds 0 through 255 and -1 as 257 separate values, which is the whole reason getchar's return type is int.
Line 4Plain char is one of these two types depending on the compiler and target, so code written with char c fails one way or the other.
EOF is sticky, and feof says why the loop ended
Shows that the end-of-file indicator stays set after the first EOF, and that ferror separates a clean end of input from a failure.
<stdio.h>
/* run it as: printf 'x' | ./demo */
int main(void)
{
int c = getchar();
printf("call 1: %d\n", c);
c = getchar();
printf("call 2: %d feof=%d ferror=%d\n", c, feof(stdin) != 0, ferror(stdin) != 0);
c = getchar(); /* indicator already set: no read is attempted */
printf("call 3: %d\n", c);
clearerr(stdin);
printf("after clearerr: feof=%d\n", feof(stdin) != 0);
return 0;
}
Example explained
Line 1Call 1 returns 120, the numeric value of the byte 'x': getchar hands back the byte itself, widened to int.
Line 2Call 2 finds nothing left, returns EOF and sets the end-of-file indicator; ferror is 0, so this is a clean end of input rather than a failed read.
Line 3Call 3 returns EOF again without asking the operating system for data, because the indicator is already set.
Line 4clearerr resets that indicator; without it feof(stdin) would keep reporting 1 for the rest of the program.
One character of lookahead with ungetc
Reads digits until a non-digit appears, then puts that byte back so the rest of the program can still see it.
<stdio.h>
<ctype.h>
/* run it as: printf '4271kg' | ./demo */
int main(void)
{
int c, digits = 0;
long n = 0;
while ((c = getchar()) != EOF && isdigit(c)) {
n = n * 10 + (c - '0');
digits++;
}
if (c != EOF)
ungetc(c, stdin); /* hand the non-digit back to the stream */
printf("number = %ld from %d digits\n", n, digits);
printf("next byte is still there: %d\n", getchar());
return 0;
}
Example explained
Line 1The loop stops on 'k', but that byte has already been consumed from the stream; character-by-character reading gives no way to look without taking.
Line 2ungetc(c, stdin) pushes it back, and the final getchar returns 107, the value of 'k', proving it is still available.
Line 3Because c is an int holding an unsigned char value, isdigit(c) is safe; a negative value other than EOF would be undefined behaviour there.
Line 4Only one character of pushback is guaranteed, so this technique covers single-character lookahead and nothing deeper.
Important notes
The standard only guarantees that EOF is a negative int constant; it is -1 on every real implementation, but compare against the macro rather than against a literal -1.
getchar reads bytes, not multibyte characters: a UTF-8 'é' arrives as two calls (0xC3 then 0xA9), so counting getchar calls counts bytes, not letters.
Common mistakes
Declaring char c instead of int c: where plain char is unsigned, EOF becomes 255, the loop never ends, and the program floods the terminal with the same byte; where it is signed, a 0xFF byte in the input silently truncates the read.
Writing while (c = getchar() != EOF): != binds tighter than =, so c gets 1 for every character and 0 at the end, and the body processes those values instead of the input.
Expecting EOF right after the last visible character typed at a terminal: the '\n' from Enter arrives first, and nothing arrives at all until Enter is pressed, so the program looks frozen until Ctrl-D is sent.
Try it yourself
Change, predict, then run
Read stdin with getchar and print the length of the longest line, not counting the newline, together with the number of characters that came after the final newline. Verify with input 'ab\ncdef\ngh' that it reports a longest line of 4 and an unterminated tail of 2.
Open the C workspaceCheck your understanding
A program uses char c; while ((c = getchar()) != EOF) putchar(c); and is built for a target where plain char is unsigned. What happens once the input runs out?
- It stops normally, because the comparison promotes c back to int and restores the value getchar returned.
- It stops early at the first input byte whose value is 255, because that byte compares equal to EOF.
- It never stops, because EOF is stored as 255 and 255 != EOF is true on every iteration.
- It fails to compile, since the int returned by getchar cannot be assigned to a char without a cast.
Show answer
Storing -1 in an 8-bit unsigned type keeps only the low bits, giving 255; the promotion in the comparison then produces 255, never -1, so the test never succeeds and the loop keeps calling getchar, which keeps returning EOF. Option 1 describes the mirror bug that appears when plain char is signed, where a 0xFF data byte promotes to -1 and ends the loop early. Both failures have the same root cause, a char cannot represent 257 distinct values, which is why c must be an int.