Standard I/O: printf, scanf, fgets, and Why gets Was Removed
What This Concept Is
<stdio.h> exposes the C Standard Library's stream I/O interface. The three implicit streams are stdin, stdout, and stderr. Each is a FILE * with a library-managed buffer.
The main operations:
- Formatted output:
printf(fmt, ...),fprintf(stream, fmt, ...),snprintf(buf, size, fmt, ...). - Formatted input:
scanf(fmt, ...),fscanf(stream, fmt, ...),sscanf(str, fmt, ...). - Line input:
fgets(buf, size, stream). Reads up tosize - 1characters or a newline, whichever comes first. Always null-terminates. - Character input/output:
getchar,putchar,fgetc,fputc. - File access:
fopen,fread,fwrite,fclose, plusferror/feoffor status.
gets(buf) used to read a line into buf without any size limit. It was deprecated in C99 and removed in C11. If you see it in old code, replace it with fgets(buf, sizeof buf, stdin) immediately.
Why It Matters Here
Almost every first C program you write does I/O, and I/O is where most early crashes appear:
printf("%d", x)whenxis along long-> undefined behaviorscanf("%d", n)instead ofscanf("%d", &n)-> undefined behaviorfgetsnot checking its return forNULLat EOF -> reading uninitialized bufferprintf(user_input)-> classic format-string vulnerability
All of these are avoidable with a few rules you learn once.
Concrete Example
Reading a line and parsing two numbers safely:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void) {
char line[256];
if (!fgets(line, sizeof line, stdin)) {
fprintf(stderr, "no input\n");
return 1;
}
/* strip trailing newline if present */
size_t n = strlen(line);
if (n > 0 && line[n - 1] == '\n') line[n - 1] = '\0';
int a, b;
if (sscanf(line, "%d %d", &a, &b) != 2) {
fprintf(stderr, "expected two integers, got: %s\n", line);
return 1;
}
printf("sum = %d\n", a + b);
return 0;
}
Why this pattern:
fgetsbounds the read and null-terminates.sscanfparses from an already-bounded buffer, so failure is recoverable.printfgets the correct%dfor anint.
Common Confusion / Misconception
"scanf is fine for interactive input." It leaves whitespace and newlines in the buffer in ways that confuse the next call. Prefer fgets + sscanf for anything non-trivial.
"printf is type-safe because of the format string." It is type-safe only if the format matches the arguments. The compiler warns via -Wformat when it can see the format string literally, but not when the format string is computed at runtime.
"gets is still there if I include the right header." No. It is gone in C11. Even in C89 code, treat it as removed.
How To Use It
Rules:
printf("%d", int_value);,printf("%ld", long_value);,printf("%zu", size_t_value);,printf("%s", "string");. Always match specifier to argument type.scanf("%d", &n);- pass an address for every%d,%s, etc.- Use
fgets(buf, sizeof buf, stdin)for line input. Always check the return value. - Use
snprintf(buf, sizeof buf, ...)instead ofsprintf. - Never pass user input directly as a format string:
printf("%s", user_input);, notprintf(user_input);. - Check
fclose,fread,fwritereturn values. Partial reads/writes happen.
Check Yourself
- Why was
getsremoved from C11? - What happens if the format specifier in
printfdoes not match the argument type? - How do you detect an EOF with
fgets?
Mini Drill or Application
Write a small program that:
- Reads lines from stdin until EOF using
fgets. - For each line, trims the trailing newline and tokenizes it with
strtokon spaces. - Prints the token count to
stdoutand errors tostderr. - Returns 0 on normal EOF, non-zero on any I/O error (check
ferror).
Compile with gcc -Wall -Wextra -std=c11 -o tokcount tokcount.c.