C
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions
-
Section 4: Arrays and Strings
-
Section 5: Pointers
-
Section 6: Memory Management
-
Section 7: Structures and Unions
-
Section 8: The Preprocessor and Build Process
-
Section 9: Standard Library: stdio.h
-
Section 10: Standard Library: stdlib.h
-
Section 11: Standard Library: string.h
-
Section 12: Standard Library: ctype.h and wctype.h
-
Section 13: Standard Library: math.h, complex.h, fenv.h, tgmath.h
-
Section 14: Standard Library: Type and Limit Headers
-
Section 15: Standard Library: Error Handling and Debugging
-
Section 16: Standard Library: Localization and Encoding
-
Section 17: Standard Library: time.h
-
Section 18: Standard Library: Concurrency (C11)
-
Section 19: POSIX and System Programming (unistd.h)
-
Section 20: More Data Structures
-
Section 21: Algorithms in C
-
Section 22: Bitwise Operations
-
Section 23: Command-Line Programs
-
Section 24: Debugging and Best Practices
-
Section 25: Compiler and Language Internals
-
Section 26: Embedded and Cross-Platform Considerations
-
Section 27: Networking Basics
-
Section 28: Practical Projects
-
Section 29: Interview Practice
-
Section 30: C23 Modern Features
-
Section 31: More Practice and Review
16: Input and Output with printf and scanf
Up until now, your programs have mostly been static—you define some variables, do some math, and print a result. But software is useless if it can't talk to the user. In C, printf and scanf are your primary tools for this, but there's a massive gap between using them to pass a homework assignment and using them in a way that won't crash the moment a user hits the wrong key.
The Temptation of the Simple Scan
If you look at most beginner tutorials, they'll show you a very direct way to get data. Let's say we're building a simple user profile tool where we need a name and an age. The naive approach looks something like this:
char name[20];
int age;
printf("Enter your name: ");
scanf("%s", name);
printf("Enter your age: ");
scanf("%d", &age);
On the surface, this is clean. It's readable. If you type "Alice" and "25", it works perfectly. I'll admit, it's tempting to leave it here because it feels intuitive. But in a production environment, this code is a liability. You're essentially trusting the user to be a perfect operator, and in my experience, users are the most unpredictable part of any system.
Where the Naive Approach Breaks
There are two main ways the code above will blow up in your face. First, look at scanf("%s", name). We've allocated 20 bytes for the name. What happens if a user enters a name that is 30 characters long? C doesn't stop them. scanf will happily keep writing data past the end of your array, overwriting other variables or crashing the program with a segmentation fault. This is a classic buffer overflow, and it's exactly how many security vulnerabilities are born.
Then there's the type mismatch. What happens if I ask for your age, and you type "twenty-five" instead of "25"? scanf("%d", &age) will fail to match the integer format. Here is the kicker: it doesn't just fail and move on. The "twenty-five" stays sitting in the input buffer. If you have another scanf call coming up, it will try to read that same "twenty-five" again, fail again, and you'll likely end up in an infinite loop or with garbage data in your variables.
Writing Code That Doesn't Trust the User
To fix this, we need to be defensive. First, we tell scanf exactly how much room we have. By adding a width specifier, we prevent the buffer overflow. Second, we actually check the return value of scanf. Most people ignore this, but scanf returns the number of items successfully matched and assigned. If it returns 0, it means the input didn't match the format you expected.
Here is how I would actually write that profile input logic:
char name[20];
int age;
printf("Enter your name: ");
// Limit input to 19 chars to leave room for the null terminator
scanf("%19s", name);
printf("Enter your age: ");
if (scanf("%d", &age) != 1) {
printf("That's not a valid number. I'll assume you're timeless.\n");
age = 0;
// Note: In a real app, you'd want to clear the buffer here
}
By using %19s, we've closed the security hole. By checking if the return value is 1, we've ensured that age actually contains a value provided by the user, rather than whatever random junk happened to be in memory at the time.
The Cost of Safety
Is this more verbose? Yes. Does it require you to think about buffer sizes and return codes? Absolutely. But that's the trade-off of working in C. You don't have a runtime environment catching these errors for you. You are the safety net. When you're writing a quick prototype, the naive way is fine, but the moment your code touches a keyboard or a network socket, you have to assume the input is malicious or mistaken. I'd rather spend an extra ten seconds writing an if statement than spend three hours debugging a random crash in a production build.
📋 Practical Task
Exercise: Robust Budget Tracker Input
Build a small program that asks the user for two pieces of information: a category name (string) and a spending amount (float). Your program must implement the following safety requirements:
- The category name should be stored in a buffer of 15 characters, and
scanfmust be restricted to prevent overflows. - The program must check if the spending amount was entered as a valid number. If the user enters text instead of a number, the program should print an error message "Invalid amount entered" and set the amount to 0.0.
- Finally, print a summary like:
"Category: [name], Spent: $[amount]".
There are no comments for now.