Module 1: C Programming Fundamentals: Case Studies
These case studies make C's basic model concrete: translation units, headers, strings, stdio, integer conversions, and undefined behavior.
Case Study 1: Header Definition Breaks The Linker
Scenario: A learner defines int debug = 1; in a header included by three .c files. Compilation works; linking fails with multiple definitions.
Source anchor: C's compile/link model is grounded in translation units and linkage; use this with gcc -E/-S/-c and linker errors as evidence.
Module concepts: declaration, definition, header, translation unit, linkage.
Wrong Approach
Put variable definitions in headers.
Better Approach
Use extern declaration in header and one definition in a .c file:
/* config.h */
extern int debug;
/* config.c */
int debug = 1;
Tradeoff Table
| Choice | Gain | Cost |
|---|---|---|
| Define in header | Looks convenient in one file | Breaks at link time across translation units |
extern + one definition | Correct linkage model | Requires a dedicated definition site |
static in header | Avoids linker clash | Creates separate copies per translation unit |
Failure Mode
The build passes compilation on every .c file, then fails at link time with multiple-definition errors that are hard to trace back to the header.
Required Artifact
Build a three-file example and annotate preprocessor, compiler, assembler, linker outputs.
Project / Capstone Connection
Use this pattern for shared configuration, logging flags, and global state in C utilities built later in the semester.
Case Study 2: Unsafe String Copy
Scenario: A CLI copies argv input into char name[32] with strcpy. Long input overwrites adjacent stack data.
Source anchor: SEI's C security discussion uses buffer overflow examples to show why copying larger strings into smaller arrays is dangerous. See SEI: Improving Security in the Latest C Standard.
Module concepts: C string, null terminator, buffer size, bounds.
Wrong Approach
"The input is probably short."
Better Approach
Track buffer capacity explicitly:
char name[32];
snprintf(name, sizeof name, "%s", argv[1]);
Tradeoff Table
| Choice | Gain | Cost |
|---|---|---|
strcpy into fixed buffer | Minimal code | No bounds protection |
snprintf with sizeof | Enforces capacity and terminator handling | Possible truncation to account for |
| Dynamic allocation from input length | Fits arbitrary input | More memory-management complexity |
Failure Mode
An unusually long argument overwrites adjacent stack state, causing crashes, silent corruption, or exploitable behavior far from the copy site.
Required Artifact
Write a string-safety checklist for input, copy, formatting, and termination.
Project / Capstone Connection
Apply this checklist to every CLI parser, config loader, and text-processing tool you build in C.
Case Study 3: scanf Leaves A Broken Input Stream
Scenario: A menu program uses scanf("%d", &choice). When the user types abc, the loop repeats forever.
Source anchor: C stdio behavior is observable through standard library docs; this case reinforces why fgets plus parsing is safer for line-oriented input.
Module concepts: stdio, formatted input, error handling, input buffer.
Wrong Approach
Assume scanf always consumes invalid input.
Better Approach
Read a full line and parse:
char line[64];
if (fgets(line, sizeof line, stdin)) {
char *end = NULL;
long value = strtol(line, &end, 10);
}
Tradeoff Table
| Choice | Gain | Cost |
|---|---|---|
Direct scanf loop | Short code for happy path | Fragile recovery on invalid input |
fgets + strtol | Clear error handling and buffer control | More parsing code |
| Custom tokenizer layer | Reusable input contract | More upfront abstraction |
Failure Mode
Invalid characters remain unread, so the loop repeatedly sees the same bad input and appears hung to the user.
Required Artifact
Write a robust menu input loop with invalid-input recovery.
Project / Capstone Connection
Use this line-oriented input pattern for shell-like tools and interactive debuggers later in the systems track.
Case Study 4: Integer Conversion Surprise
Scenario: A function compares signed int len = -1 with unsigned size_t capacity = 16. The comparison behaves unexpectedly.
Source anchor: C integer conversions are a core language rule; use compiler warnings and -Wall -Wextra -Wconversion as evidence.
Module concepts: signed/unsigned conversion, integer rank, warnings.
Wrong Approach
"The comparison reads like math."
Better Approach
Use compatible types and validate boundaries:
if (len < 0) return ERROR;
if ((size_t)len > capacity) return TOO_LARGE;
Tradeoff Table
| Choice | Gain | Cost |
|---|---|---|
| Mixed signed/unsigned compare | No extra code | Easy to misread and miscompile mentally |
| Validate sign before cast | Makes intent explicit | Requires disciplined boundary checks |
| Standardize type choices earlier | Fewer conversions overall | Can require wider API cleanup |
Failure Mode
A negative sentinel converts to a huge unsigned value, so range checks or loop bounds pass when they should fail.
Required Artifact
Create a conversion table for five signed/unsigned expressions and compiler warnings.
Project / Capstone Connection
Carry this into buffer-length, file-size, and syscall-wrapper code where signed error returns meet unsigned sizes.
Case Study 5: Macro With Double Evaluation
Scenario: #define MAX(a,b) ((a) > (b) ? (a) : (b)) is called as MAX(i++, j++).
Source anchor: The C preprocessor performs textual substitution, not function-call evaluation.
Module concepts: macro expansion, side effects, inline function.
Wrong Approach
Treat macros like functions.
Better Approach
Avoid side-effect arguments or use functions where possible:
static inline int max_int(int a, int b) {
return a > b ? a : b;
}
Tradeoff Table
| Choice | Gain | Cost |
|---|---|---|
| Text macro | Generic and zero call syntax overhead | Side effects and type surprises |
static inline function | Single evaluation and debuggable behavior | Type-specific unless wrapped deliberately |
| Precompute arguments before macro | Keeps existing macro | Depends on caller discipline |
Failure Mode
One operand is evaluated twice, so counters advance unexpectedly and behavior changes when debugging or optimizing.
Required Artifact
Run gcc -E and show the expanded macro that evaluates an argument twice.
Project / Capstone Connection
Use this case when deciding whether helper logic in later C projects belongs in macros, inline functions, or normal functions.
Source Map
| Source | Use it for |
|---|---|
| SEI C security discussion | buffer overflow motivation |
| GCC warning options | compiler diagnostics |
| C standard drafts / cppreference C | language and library reference |
Completion Standard
- At least three artifacts are completed.
- At least one artifact shows compile/link stages.
- At least one artifact uses compiler warnings to catch a bug.