C Input and Output - printf scanf stdin stdout Guide 2026
|

C Input and Output: printf, scanf, Format Specifiers Guide 2026

C Input and Output

Every useful program needs to communicate with the outside world — display results, read user input, or exchange data with files and devices. In C, all input and output operations flow through the standard I/O library defined in <stdio.h>. This library treats everything as a stream of bytes, whether you are printing to the terminal, reading from the keyboard, or writing to a file.

The two functions you will use most often are printf for output and scanf for input. Both use format strings — a powerful (but sometimes dangerous) mechanism that lets you control exactly how data is formatted and parsed.

Standard Streams

When a C program starts, three streams are automatically opened:

stdin (standard input) is where scanf and getchar read from. By default, this is your keyboard. stdout (standard output) is where printf writes to. By default, this is your terminal. stderr (standard error) is for error messages. It is also your terminal by default, but it is unbuffered, meaning errors appear immediately even if stdout is redirected.

#include <stdio.h>

int main(void) {
    fprintf(stdout, "Normal output\n");
    fprintf(stderr, "Error message\n");
    return 0;
}

From the command line, you can redirect these streams:

./program > output.txt        # redirect stdout to file
./program 2> errors.txt       # redirect stderr to file
./program < input.txt         # redirect stdin from file
./program < input.txt > output.txt 2> errors.txt  # all three

printf: Formatted Output

The printf function sends formatted text to stdout. Its first argument is a format string containing literal text and format specifiers (placeholders starting with %):

int age = 25;
float gpa = 3.85f;
char grade = 'A';

printf("Age: %d, GPA: %.2f, Grade: %c\n", age, gpa, grade);

Each % specifier consumes the next argument in order. The number and types of arguments must match the specifiers exactly — mismatches cause undefined behavior.

Format Specifiers Reference

Here are the format specifiers you will use constantly:

Integer Specifiers

int n = 42;
unsigned int u = 42;
long l = 42L;
long long ll = 42LL;

printf("%d\n", n);      // signed decimal: 42
printf("%i\n", n);      // same as %d: 42
printf("%u\n", u);      // unsigned decimal: 42
printf("%o\n", n);      // octal: 52
printf("%x\n", n);      // hex lowercase: 2a
printf("%X\n", n);      // hex uppercase: 2A
printf("%ld\n", l);     // long
printf("%lld\n", ll);   // long long

Floating-Point Specifiers

double pi = 3.14159265358979;

printf("%f\n", pi);      // fixed-point: 3.141593 (6 decimal places default)
printf("%.2f\n", pi);    // 2 decimal places: 3.14
printf("%.10f\n", pi);   // 10 decimal places: 3.1415926536
printf("%e\n", pi);      // scientific: 3.141593e+00
printf("%E\n", pi);      // scientific uppercase: 3.141593E+00
printf("%g\n", pi);      // shortest representation: 3.14159

Character and String Specifiers

char c = 'X';
char name[] = "SudoFlare";

printf("%c\n", c);       // single character: X
printf("%s\n", name);    // string: SudoFlare
printf("%.4s\n", name);  // first 4 chars: Sudo

Other Useful Specifiers

int *ptr = &n;
size_t sz = sizeof(n);

printf("%p\n", (void*)ptr);  // pointer address: 0x7ffd...
printf("%zu\n", sz);         // size_t: 4
printf("%%\n");              // literal percent sign: %
printf("%n");                 // DON'T USE: writes bytes count (security risk)

Width and Alignment

You can control the minimum width and alignment of output:

int val = 42;

printf("[%10d]\n", val);   // right-aligned, width 10: [        42]
printf("[%-10d]\n", val);  // left-aligned, width 10:  [42        ]
printf("[%010d]\n", val);  // zero-padded, width 10:   [0000000042]
printf("[%+d]\n", val);    // always show sign:        [+42]
printf("[%+d]\n", -val);   // always show sign:        [-42]

For floating-point numbers, you can control both width and precision:

double price = 9.99;
printf("[%10.2f]\n", price);   // [      9.99]
printf("[%-10.2f]\n", price);  // [9.99      ]

Width formatting is essential for creating aligned tables and reports. When building command-line tools, formatted output makes your programs look professional.

scanf: Formatted Input

The scanf function reads formatted input from stdin. It uses the same format specifiers as printf, but with one critical difference — you must pass the address of the variable using the & operator:

int age;
printf("Enter your age: ");
scanf("%d", &age);  // & is required for non-array types!
printf("You are %d years old\n", age);

Forgetting the & is one of the most common C bugs. Without it, scanf writes to a random memory address, causing crashes or silent data corruption.

Reading Different Types

int n;
float f;
double d;
char c;
char name[50];

scanf("%d", &n);        // read integer
scanf("%f", &f);        // read float
scanf("%lf", &d);       // read double (note: %lf, not %f!)
scanf(" %c", &c);       // read char (space before %c skips whitespace)
scanf("%49s", name);     // read string (no & needed for arrays!)
                         // 49 = buffer size - 1 for null terminator

Notice that scanf uses %lf for double, while printf uses %f for both float and double. This inconsistency is a historical artifact and trips up beginners constantly.

The scanf Return Value

scanf returns the number of items successfully read. Always check this value:

int n;
printf("Enter a number: ");
if (scanf("%d", &n) != 1) {
    fprintf(stderr, "Error: invalid input\n");
    return 1;
}
printf("You entered: %d\n", n);

If the user types “abc” instead of a number, scanf returns 0 (no items matched), and n remains uninitialized. Without the check, your program would continue with garbage data.

scanf Pitfalls and Solutions

The Newline Problem

When you type a number and press Enter, scanf("%d") consumes the number but leaves the newline character \n in the input buffer. A subsequent scanf("%c") immediately reads that leftover newline:

int age;
char grade;
printf("Age: ");
scanf("%d", &age);      // reads "25", leaves "\n" in buffer
printf("Grade: ");
scanf("%c", &grade);    // reads the leftover "\n", not the user's input!

// Fix: add a space before %c
scanf(" %c", &grade);   // space skips all whitespace including newlines

Buffer Overflow with Strings

Never use scanf("%s", name) without a width limit. If the user enters a string longer than your buffer, scanf writes past the end of the array — a buffer overflow that can crash your program or be exploited for security attacks:

char name[20];

// DANGEROUS:
scanf("%s", name);      // no limit — buffer overflow possible

// SAFE:
scanf("%19s", name);    // read at most 19 chars (leave room for \0)

Reading Entire Lines

scanf("%s") stops at the first whitespace, so it cannot read a full name like “John Doe”. Use fgets instead:

char line[100];
printf("Enter your full name: ");
fgets(line, sizeof(line), stdin);

// fgets includes the trailing newline — remove it
size_t len = strlen(line);
if (len > 0 && line[len - 1] == '\n') {
    line[len - 1] = '\0';
}
printf("Hello, %s!\n", line);

fgets is safer than scanf for string input because it always respects the buffer size limit. For production code, prefer fgets over scanf for reading strings.

Character I/O: getchar and putchar

For reading and writing single characters, getchar and putchar are the simplest functions:

int ch;

printf("Press a key: ");
ch = getchar();
printf("You pressed: ");
putchar(ch);
putchar('\n');

Note that getchar returns an int, not a char. This is because it returns EOF (typically -1) when there is no more input, and that value cannot fit in a char.

Reading Input Character by Character

// Count characters, words, and lines (mini wc)
int ch, chars = 0, words = 0, lines = 0;
int in_word = 0;

while ((ch = getchar()) != EOF) {
    chars++;
    if (ch == '\n') lines++;
    if (ch == ' ' || ch == '\n' || ch == '\t') {
        in_word = 0;
    } else if (!in_word) {
        in_word = 1;
        words++;
    }
}
printf("%d lines, %d words, %d chars\n", lines, words, chars);

This pattern — reading characters in a loop until EOF — is the foundation of many text processing programs. It is how utilities like cat, wc, and grep work internally.

Output Buffering

stdout is line-buffered when connected to a terminal (output is flushed when a newline is printed) and fully-buffered when redirected to a file (output is flushed when the buffer is full or the program exits).

This can cause issues when you want output to appear immediately without a newline:

printf("Loading...");  // may NOT appear immediately (no newline!)
fflush(stdout);         // force the output to appear now
sleep(2);
printf(" done!\n");    // this flushes because of \n

Always call fflush(stdout) after printing prompts that do not end with \n. Otherwise, the user might see a blank screen while the program waits for input.

fprintf and fscanf

While printf and scanf work with stdout and stdin, their counterparts fprintf and fscanf work with any file stream:

// Print to stderr (useful for error messages)
fprintf(stderr, "Error: file not found\n");

// Print to a file
FILE *fp = fopen("log.txt", "w");
if (fp) {
    fprintf(fp, "Log entry: value = %d\n", 42);
    fclose(fp);
}

We will cover file I/O in detail in a later lesson. For now, just know that printf(...) is equivalent to fprintf(stdout, ...).

sprintf and snprintf

sprintf writes formatted output to a string buffer instead of stdout:

char buffer[100];
int day = 25, month = 5, year = 2026;
sprintf(buffer, "%04d-%02d-%02d", year, month, day);
printf("Date: %s\n", buffer); // Date: 2026-05-25

However, sprintf can overflow the buffer if the output is too long. Always use snprintf instead, which takes a size limit:

char buffer[20];
snprintf(buffer, sizeof(buffer), "Very long string that could overflow...");
// snprintf truncates safely — buffer always null-terminated

Escape Sequences

C supports these escape sequences in strings:

printf("Newline: line1\nline2\n");
printf("Tab: col1\tcol2\n");
printf("Backslash: C:\\Users\n");
printf("Single quote: \'\n");
printf("Double quote: \"hello\"\n");
printf("Null character: \0\n");
printf("Alert (beep): \a\n");
printf("Backspace: \b\n");
printf("Carriage return: \r\n");
printf("Hex character: \x41\n");  // 'A'
printf("Octal character: \101\n"); // 'A'

Practical Example: Interactive Calculator

Let us put everything together with a simple calculator that reads two numbers and an operator:

#include <stdio.h>

int main(void) {
    double a, b, result;
    char op;

    printf("Enter expression (e.g., 3.5 + 2.1): ");
    if (scanf("%lf %c %lf", &a, &op, &b) != 3) {
        fprintf(stderr, "Error: invalid input\n");
        return 1;
    }

    switch (op) {
        case '+': result = a + b; break;
        case '-': result = a - b; break;
        case '*': result = a * b; break;
        case '/':
            if (b == 0.0) {
                fprintf(stderr, "Error: division by zero\n");
                return 1;
            }
            result = a / b;
            break;
        default:
            fprintf(stderr, "Error: unknown operator '%c'\n", op);
            return 1;
    }

    printf("%.6g %c %.6g = %.6g\n", a, op, b, result);
    return 0;
}

This program demonstrates format specifiers (%lf for reading doubles, %.6g for smart output), error handling (checking scanf return value), and fprintf(stderr, ...) for error messages.

Common Mistakes with printf and scanf

Wrong Format Specifier

double x = 3.14;
printf("%d\n", x);   // WRONG: %d expects int, not double
printf("%f\n", x);   // CORRECT

Mismatched format specifiers cause undefined behavior. The compiler reads the wrong number of bytes from the argument, producing garbage output or crashes. Compile with -Wformat (included in -Wall) to catch these.

Missing & in scanf

int n;
scanf("%d", n);    // WRONG: missing & — undefined behavior
scanf("%d", &n);   // CORRECT

Format String Vulnerability

char user_input[100];
fgets(user_input, sizeof(user_input), stdin);
printf(user_input);  // DANGEROUS: format string attack!
printf("%s", user_input); // SAFE: user input treated as data

Never pass user input directly as the format string to printf. An attacker can use specifiers like %x to read the stack or %n to write to memory. This is a real security vulnerability that has been exploited in production software.

What Comes Next

You can now read input and display formatted output. The next lesson covers if / else — conditional branching that lets your program make decisions based on the data it processes. Combined with I/O, you will build programs that respond differently to different inputs.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *