C-Specific Pitfalls in studies¶
Most study problems are language-neutral in statement, but C imposes its own layer of correctness concerns — integer overflow, char* vs char[], size_t vs int for indices, and the discipline of malloc/free under time pressure. This file inventories the traps that cost people points in real studies, so you can spot them before they bite.
Trap 1: Integer Overflow in DP and Math¶
The single most common bug in C study solutions. The study partner sees you write int dp[100][100] and knows what’s coming.
Where It Bites¶
Counting DPs: “count number of ways to X” — the count grows exponentially. Overflows
intaround problem sizes as small as N=30. Uselong longand take mod10^9 + 7when the problem asks for it.Sum of large ranges: Sum of N up to 10^6 with values up to 10^9 overflows
int. Uselong long.Product of two ints:
a * bwhereaandbare each near INT_MAX. Cast first:(long long)a * b.Binary search midpoint:
(lo + hi) / 2overflows when both are large. Uselo + (hi - lo) / 2.Sentinel values in DP:
INT_MAXas “infinity” and then you dodp[i] + 1— overflows toINT_MIN. UseINT_MAX / 2orINT_MAX - 1000as a safer sentinel.
The Rule¶
Default to long long for any accumulator or DP value unless you’re certain of bounds. Convert back to int at the return boundary. The cost of long long on a 64-bit machine is essentially nil.
long long dp[n+1];
// ... compute ...
return (int)(dp[n] % <phone_number_or_numberic_id_or_random_id_12>);
Trap 2: size_t vs int for Indices¶
size_t is unsigned, int is signed. Mixing them is legal but full of traps.
The Wraparound Bug¶
for (size_t i = n - 1; i >= 0; i--) { /* ... */ } // INFINITE LOOP
size_t never goes below 0; it wraps to SIZE_MAX. Two fixes:
for (size_t i = n; i-- > 0; ) { /* use i */ } // safe idiom
for (int i = (int)n - 1; i >= 0; i--) { /* ... */ } // cast to int
The Signed-Unsigned Comparison Warning¶
int i;
size_t n = strlen(s);
for (i = 0; i < n; i++) { /* comparison warning: -Wsign-compare */ }
The implicit conversion promotes i to size_t, which is fine if i is nonnegative but scary in general. Just use the same type on both sides.
The Rule¶
Use
intfor indices in study code. Simpler, no wraparound, matches LC’s function signatures.Use
size_tforlibpreppublic APIs (matchingstrlen,sizeofconventions), but internally useintwhen you’re doing arithmetic that might go negative.Never mix them in a single expression without an explicit cast.
Trap 3: char* vs char[] — The Modification Trap¶
char *s = "hello"; // pointer to string LITERAL in .rodata
s[0] = 'H'; // SIGSEGV — undefined behavior
char t[] = "hello"; // array on stack, initialized by copy
t[0] = 'H'; // fine
Most LC problem signatures give you a char* that IS modifiable (they allocate it), but always check. And when you return a char*, decide whether it points to caller-owned, malloc’d, or static memory and document it.
Trap 4: The free Discipline in a Timed study¶
Under time pressure, free calls are the first thing people forget. In C studies (not LC — real studies with a human), the study partner will absolutely check.
The Habits That Save You¶
Every
mallocgets paired with afree— write them at the same time. If you allocate at line 20, write the free at line 40 immediately, even before line 25.free-then-NULL:free(p); p = NULL;— makes double-free crash loudly instead of silently.A
destroy_Xfunction per struct that allocates: ifX_newmallocs,X_freefrees. Pair them.Test locally with ASan. If time permits, run
-fsanitize=addressbefore submitting.
Trap 5: strncmp Off-by-One and Null-Termination¶
strncmp(a, b, n) compares at most n characters. If both a and b are null-terminated within n, works fine. If neither is, works fine. If one is and the other isn’t at position n, it’s still safe.
But strncpy(dst, src, n) does NOT null-terminate if strlen(src) >= n. This is one of the C standard’s original sins.
char buf[10];
strncpy(buf, "a very long string", 10); // buf is NOT null-terminated
printf("%s\n", buf); // undefined behavior
// Fix:
strncpy(buf, "a very long string", sizeof buf - 1);
buf[sizeof buf - 1] = '\0';
// Or use snprintf:
snprintf(buf, sizeof buf, "%s", "a very long string");
Rule: prefer snprintf over strncpy for building strings; use strncmp for comparison. If you must use strncpy, always follow with an explicit null-terminator.
Trap 6: Recursion Stack Overflow¶
Default Linux stack is 8 MB. Each recursive call is ~1 KB (locals + saved registers + arguments). ~8000 levels of recursion and you segfault.
Where It Bites¶
Recursive DFS on a linked-list-shaped graph or tree: V = 10^5 vertices, chain shape, depth 10^5, blows the stack.
Recursive backtracking with huge branching: usually depth-limited by the problem, so less risk, but check.
bst_insert(root, val)on an already-sorted input: creates a skewed BST of depth N. Insert N elements = N^2 total function calls, depth N. Both time and stack bomb.
The Fix¶
Rewrite iteratively with an explicit stack from libprep::stack. Uglier code, but no stack limit. See 06_graphs_bfs_dfs.md for the iterative DFS template.
Trap 7: Uninitialized Local Variables¶
int sum;
for (int i = 0; i < n; i++) sum += arr[i]; // sum starts at garbage
BSS globals are zero-initialized; stack locals are not. Always initialize:
int sum = 0;
Compile with -Wall -Wuninitialized and it warns. study partners watching your terminal will notice you didn’t set -Wall.
Trap 8: Array Decay in Function Parameters¶
void f(int arr[10]) { printf("%zu\n", sizeof arr); } // prints 8 (or 4), NOT 40
An array parameter decays to a pointer. sizeof arr inside f is the pointer size, not the array size. Always pass the length as a separate parameter:
void f(int *arr, size_t n) { /* ... */ }
This is why every LC array function signature takes both the array and its length. Get in the habit.
Trap 9: qsort Comparator Function¶
qsort takes a comparator that returns int — negative, zero, or positive. Do not write return a - b; when a and b are int — their difference can overflow int:
int cmp_int(const void *a, const void *b) {
int x = *(const int*)a, y = *(const int*)b;
return x - y; // WRONG: overflow if x - y overflows
return (x > y) - (x < y); // CORRECT: no overflow, exactly -1/0/1
}
The (x > y) - (x < y) idiom is the safe form. Memorize it.
Trap 10: Returning Pointer to Local Variable¶
int *make_array(void) {
int arr[10] = {0};
return arr; // UNDEFINED BEHAVIOR: arr is out of scope after return
}
Every C programmer writes this bug exactly once, watches the mysterious data corruption, and never writes it again. Return malloc’d memory and document who frees. Or take an output buffer as a parameter (void make_array(int *out)).
Trap 11: NULL vs 0 vs '\0'¶
All three are integer zero, but semantically:
NULLfor pointers.0for arithmetic.'\0'for characters (specifically the null terminator).
Code that says if (ptr == 0) works, but if (!ptr) or if (ptr == NULL) reads better.
Trap 12: The ? : Precedence Landmine¶
int x = a > b ? a : b + 1; // is this (a > b ? a : b) + 1 or a > b ? a : (b + 1)?
Answer: a > b ? a : (b + 1). The ternary has very low precedence. Parenthesize when in doubt — costs nothing, reads clearer:
int x = (a > b) ? a : (b + 1);
The Meta-Rule¶
Most of these traps have one thing in common: the compiler warns you. Compile with -Wall -Wextra -Wpedantic -fsanitize=address,undefined and half of these bugs never make it to a run. In an study, mentioning that you’d normally compile with these flags scores real points — it signals that you take C’s sharp edges seriously.
What Most People Get Wrong About This¶
They memorize the language spec and forget the tooling. C without -Wall -Wextra -Wpedantic -fsanitize=address is nuclear weapons without controls; C with them is a professional tool. study partners who write C for a living know this distinction, and hearing you cite it moves you from “knows the syntax” to “knows the practice.”
Return to README.md · Next: projects.md