Basic Input and Output in C: Complete Tutorial
Basic Input and Output in C: A Comprehensive Tutorial
Input and output (I/O) operations are fundamental to C programming. They enable communication between a program and its environment: reading data from users, files, or devices, and displaying results. In C, I/O is handled through the standard library, primarily declared in <stdio.h>. This tutorial covers the core I/O functions, their syntax, safe usage, common pitfalls, and best practices for writing robust C code.
Conceptual Introduction & Fundamentals
What are Input and Output in C?
Input operations allow a program to receive data from the outside world (keyboard, files, network). Output operations send data from the program to the user (screen, files, printers). In C, all I/O is performed on streams—abstract representations of input sources or output destinations.
Standard Streams
The C standard library defines three standard streams automatically opened when a program starts:
| Stream | Purpose | Default Device |
|---|---|---|
stdin | Standard input | Keyboard |
stdout | Standard output | Screen |
stderr | Standard error (unbuffered) | Screen |
These streams are of type FILE * and are declared in <stdio.h>. They can be redirected by the shell.
Formatted vs. Unformatted I/O
- Formatted I/O converts data to/from human-readable text using format specifiers. Functions:
printf,fprintf,sprintf,scanf,fscanf,sscanf. - Unformatted I/O reads or writes raw bytes without conversion. Functions:
fgets,fputs,getchar,putchar,fread,fwrite.
The Standard I/O Library (<stdio.h>)
The <stdio.h> header declares functions for input/output, including:
printfandfprintffor formatted output.scanfandfscanffor formatted input (use with caution).fgetsandfputsfor line-oriented string I/O.getcharandputcharfor character I/O.fopen,fclose, etc., for file I/O (covered in a separate tutorial).
All functions return a value that should be checked to detect errors (e.g., EOF or NULL).
Syntax & Core Mechanics
Output: printf()
The printf function writes formatted output to stdout.
Prototype:
c
int printf(const char *format, ...);
It returns the number of characters printed, or a negative value on error.
Format specifiers are placeholders that begin with % and specify how to format the corresponding argument. Common specifiers:
| Specifier | Type |
|---|---|
%d or %i | Signed integer |
%u | Unsigned integer |
%f | Floating-point (decimal notation) |
%e or %E | Floating-point (scientific notation) |
%g or %G | Uses %f or %e depending on value |
%c | Character |
%s | String (null-terminated) |
%p | Pointer address |
%x or %X | Hexadecimal integer |
%% | Literal % |
Examples:
c
#include <stdio.h> int main(void) { int age = 22; double pi = 3.14159; char grade = 'A'; char name[] = "Alice"; // Print text printf("Hello, World!\n"); // Print integer printf("Age: %d\n", age); // Print double with precision printf("Pi: %.2f\n", pi); // Print character and string printf("Grade: %c, Name: %s\n", grade, name); // Print multiple values printf("%s is %d years old and scored %c.\n", name, age, grade); return 0; } Expected output:
text
Hello, World! Age: 22 Pi: 3.14 Grade: A, Name: Alice Alice is 22 years old and scored A.
Format flags: You can control width, precision, alignment, and more. Example: %10d prints an integer right-aligned in a field of width 10; %-10s left-aligns a string; %08.3f zero-pads to width 8 with 3 decimal places.
Output: fputs()
fputs writes a string (without formatting) to a specified stream. It does not append a newline automatically.
Prototype:
c
int fputs(const char *str, FILE *stream);
To print to the console, use stdout.
Example:
c
#include <stdio.h> int main(void) { fputs("This is a string without newline.", stdout); fputs(" This continues on the same line.\n", stdout); fputs("New line after this.\n", stdout); return 0; } Expected output:
text
This is a string without newline. This continues on the same line. New line after this.
Input: scanf()
scanf reads formatted input from stdin. It is powerful but error-prone; the C community often recommends using fgets followed by parsing functions (like strtol, strtod) for robust input.
Prototype:
c
int scanf(const char *format, ...);
It returns the number of successfully matched input items, or EOF on input failure.
Basic usage:
c
#include <stdio.h> int main(void) { int age; printf("Enter your age: "); if (scanf("%d", &age) == 1) { printf("Age is: %d\n", age); } else { printf("Invalid input.\n"); } return 0; } Sample run:
text
Enter your age: 25 Age is: 25
Important: scanf leaves the newline in the input buffer, causing problems when mixed with fgets. It can also overflow strings if %s is used without a width limit.
Safer Input: fgets()
fgets reads a line from a stream into a buffer, including the newline (if it fits). It is the recommended way to read strings in C.
Prototype:
c
char *fgets(char *str, int n, FILE *stream);
It reads at most n-1 characters, leaving room for the null terminator. Returns NULL on error or end-of-file.
Example:
c
#include <stdio.h> #include <string.h> int main(void) { char name[20]; printf("Enter your name: "); if (fgets(name, sizeof(name), stdin) != NULL) { // Remove trailing newline (if present) name[strcspn(name, "\n")] = '\0'; printf("Hello, %s!\n", name); } else { printf("Error reading input.\n"); } return 0; } Sample run:
text
Enter your name: John Doe Hello, John Doe!
Character I/O: getchar() and putchar()
getchar reads a single character from stdin; putchar writes a single character to stdout.
Prototypes:
c
int getchar(void); int putchar(int c);
Example:
c
#include <stdio.h> int main(void) { int ch; printf("Enter a character: "); ch = getchar(); if (ch != EOF) { printf("You entered: "); putchar(ch); putchar('\n'); } return 0; } Sample run:
text
Enter a character: Z You entered: Z
Full Practical Code Example with Explanation
This program demonstrates safe and robust input/output in C. It reads a user’s name, age, and height using fgets for strings and strtol/strtod for numeric parsing, then displays a formatted summary.
Program: User Information Collector
c
/** * user_info.c * Demonstrates robust input/output in C. * Uses fgets for all input and strtol/strtod for safe numeric parsing. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <limits.h> #define MAX_NAME_LEN 50 #define MAX_LINE_LEN 100 /* Function prototypes */ int readInteger(const char *prompt, int *outValue); int readDouble(const char *prompt, double *outValue); int main(void) { char name[MAX_NAME_LEN]; int age; double height; char buffer[MAX_LINE_LEN]; // temporary buffer for reading lines /* Read name using fgets */ printf("Enter your full name: "); fflush(stdout); if (fgets(name, sizeof(name), stdin) == NULL) { fprintf(stderr, "Error reading name.\n"); return EXIT_FAILURE; } /* Remove trailing newline */ name[strcspn(name, "\n")] = '\0'; /* Read age with validation */ if (readInteger("Enter your age: ", &age) != 0) { fprintf(stderr, "Failed to read age.\n"); return EXIT_FAILURE; } /* Read height with validation */ if (readDouble("Enter your height in meters (e.g., 1.75): ", &height) != 0) { fprintf(stderr, "Failed to read height.\n"); return EXIT_FAILURE; } /* Output summary using printf */ printf("\n--- User Summary ---\n"); printf("Name: %s\n", name); printf("Age: %d years\n", age); printf("Height: %.2f m\n", height); return EXIT_SUCCESS; } /* Reads an integer from stdin using fgets and strtol. * Returns 0 on success, -1 on failure. * On success, stores the value in outValue. */ int readInteger(const char *prompt, int *outValue) { char buffer[MAX_LINE_LEN]; char *endPtr; long val; while (1) { printf("%s", prompt); fflush(stdout); if (fgets(buffer, sizeof(buffer), stdin) == NULL) { return -1; // I/O error or EOF } /* Check if line is too long (no newline and buffer full) */ if (strchr(buffer, '\n') == NULL && !feof(stdin)) { /* Discard rest of line to avoid affecting next input */ int ch; while ((ch = getchar()) != '\n' && ch != EOF); printf("Input too long, please try again.\n"); continue; } /* Convert to long with error checking */ errno = 0; val = strtol(buffer, &endPtr, 10); /* Check for conversion errors */ if (endPtr == buffer) { printf("No digits found. Please enter a valid integer.\n"); continue; } /* Check for extra characters after number (except newline) */ if (*endPtr != '\n' && *endPtr != '\0') { printf("Invalid characters after number. Please try again.\n"); continue; } /* Check for out-of-range (errno set or val outside int range) */ if (errno == ERANGE || val < INT_MIN || val > INT_MAX) { printf("Number out of range for int. Please try again.\n"); continue; } *outValue = (int)val; return 0; } } /* Reads a double from stdin using fgets and strtod. * Returns 0 on success, -1 on failure. * On success, stores the value in outValue. */ int readDouble(const char *prompt, double *outValue) { char buffer[MAX_LINE_LEN]; char *endPtr; double val; while (1) { printf("%s", prompt); fflush(stdout); if (fgets(buffer, sizeof(buffer), stdin) == NULL) { return -1; } /* Check for overlong line */ if (strchr(buffer, '\n') == NULL && !feof(stdin)) { int ch; while ((ch = getchar()) != '\n' && ch != EOF); printf("Input too long, please try again.\n"); continue; } errno = 0; val = strtod(buffer, &endPtr); if (endPtr == buffer) { printf("No digits found. Please enter a valid number.\n"); continue; } if (*endPtr != '\n' && *endPtr != '\0') { printf("Invalid characters after number. Please try again.\n"); continue; } if (errno == ERANGE) { printf("Number out of range for double. Please try again.\n"); continue; } *outValue = val; return 0; } } Explanation
fgetsfor all string input: Prevents buffer overflow.strtolandstrtodfor numeric conversion: Provide full error detection (no digits, extra characters, range errors).- Looping on invalid input: The helper functions keep prompting until valid input is received.
- Handling overlong lines: Detects if input line was truncated and flushes the remaining characters to avoid affecting subsequent reads.
- Output with
printf: Uses format specifiers to display the collected data. - Error handling: Returns from
mainwithEXIT_FAILUREif initial reads fail.
Expected Output
Sample interaction:
text
Enter your full name: Jane Doe Enter your age: 25 Enter your height in meters (e.g., 1.75): 1.68 --- User Summary --- Name: Jane Doe Age: 25 years Height: 1.68 m
If invalid input:
text
Enter your age: abc No digits found. Please enter a valid integer. Enter your age: 25 ...
Common Pitfalls, Edge Cases & Memory Considerations
1. Buffer Overflow with scanf("%s", ...)
Using scanf with %s reads a string until whitespace, without any length limit. A user entering more characters than the buffer can hold causes stack corruption.
c
char name[10]; scanf("%s", name); // DANGEROUS Solution: Use fgets(name, sizeof(name), stdin) or specify width in scanf: scanf("%9s", name).
2. Mixing scanf and fgets
scanf leaves the newline character in the input buffer. A subsequent fgets will read that newline and appear to skip input.
c
int age; char name[20]; scanf("%d", &age); fgets(name, sizeof(name), stdin); // reads leftover newline Solution: Always flush the input buffer after scanf (e.g., while ((c = getchar()) != '\n' && c != EOF);) or use fgets for all input and parse with sscanf, strtol, etc.
3. Not Checking Return Values
printf, fgets, scanf, etc., can fail. Ignoring return values may lead to using uninitialized variables or infinite loops.
c
int age; scanf("%d", &age); // If input is not a number, age is uninitialized Solution: Always check the return value. For fgets, check for NULL. For scanf, ensure the expected number of items was read.
4. Trailing Newline with fgets
fgets includes the newline character if it fits. This can cause unexpected output or string comparison failures.
Solution: Remove the newline: str[strcspn(str, "\n")] = '\0';
5. Format String Vulnerabilities
Passing user input directly as the format string to printf is a severe security risk.
c
printf(userInput); // DANGEROUS: user can include %s, %x, etc.
Solution: Always use a fixed format string: printf("%s", userInput);
6. Integer Parsing without Range Checking
atoi or sscanf with %d may not detect overflow, leading to undefined behavior.
Solution: Use strtol with errno and range checks, as shown in the practical example.
7. Unbuffered Output and Flushing
Output to stdout may be buffered, so prompts might not appear before input is requested if using scanf after printf. This can confuse users.
Solution: Call fflush(stdout) after printing prompts when using scanf or other input functions that don’t automatically flush stdout.
8. Character Input Buffering
getchar reads one character but the user must press Enter, so the newline remains. This can cause issues in loops.
Solution: Handle newline characters explicitly, or use line-based input with fgets.
9. Using gets (Removed in C11)
gets was inherently unsafe because it cannot limit input length. It was removed from the C11 standard.
Solution: Never use gets. Use fgets.
10. Memory Considerations
- Stack buffers: Large local arrays in functions can cause stack overflow. Use dynamic allocation (
malloc) for large buffers, or declare themstatic. - Input size: Assume user input may be arbitrarily long. Always specify buffer sizes to
fgetsand handle truncation. - Return values: Proper checking prevents use of uninitialized memory, which can lead to crashes or security holes.
Summary & Best Practices
Basic input and output in C is built around the <stdio.h> library. Formatted output via printf is straightforward, but input requires care to avoid buffer overflows and parsing errors. The safest approach is to use fgets for all input and strtol/strtod for numeric conversion.
Key Takeaways
- Use
printffor formatted output; know common format specifiers. - Use
fputsfor simple string output to streams. - Avoid
scanffor strings; usefgetsinstead. - For integers and doubles, use
fgets+strtol/strtodwith error checking. - Always check return values of I/O functions.
- Remove trailing newlines from
fgetsinput. - Flush
stdoutbefore input prompts to ensure visibility.
Best Practices
- Prefer
fgetsoverscanforgetsfor all user input. - Use
strtol,strtoul,strtodfor converting strings to numbers, and check for errors. - Check return values of all I/O functions; handle
NULLandEOF. - Limit string inputs using the buffer size parameter in
fgets. - Remove newline from
fgetsresult if not needed. - Flush
stdoutafter printing prompts without newline. - Never use user input as a format string in
printf. - Use
snprintffor formatted output into a buffer to avoid overflow. - For large data, use dynamic memory allocation rather than huge stack arrays.
- Compile with warnings (
-Wall -Wextra) to catch unused return values and other issues.
By following these guidelines, you’ll write C programs that handle input and output safely and robustly, even in the face of unexpected user input.



