The C standard library is 50 years old and we're still writing it like it's 1974. I've reviewed enough systems code to know the specific crimes that kill readability — and most of them have a name, a pattern, and a fix that takes 30 seconds.

Someone merged a 4,000-line PR last week with no comments, variable names like tmp2, tmp3, tmp4, and a function called do_thing(). It passed review. It's in production. This is the water we swim in.
I'm not here to rant about C being old. C is beautiful when written well. The problem is the culture that's grown around it — the implicit badge of honor for writing code only you can read. Let's actually look at what this looks like in the wild.
This pattern shows up everywhere in embedded and systems code:
void process(void *data, int type) {
if (type == 1) {
struct Foo *f = (struct Foo *)data;
} else if (type == 2) {
struct Bar *b = (struct Bar *)data;
}
}You've just reinvented dynamic dispatch, badly. No type safety. No documentation. The caller has to read the entire function body to know what they're passing. The Linux kernel uses tagged unions for a reason — look at how sk_buff handles this in net/core/skbuff.c. It's not elegant but at least it's auditable.

#define MAX(a, b) a > b ? a : b
int x = MAX(i++, j++);Classic. Both i and j get incremented twice depending on which branch executes. The fix — wrapping args in parens, wrapping the whole thing — is a 10-second job. The bug it creates can take a week to find. GCC's __typeof__ extension exists specifically for this. Use it or use an inline function. This isn't 1989.
In 2019, a 200-person fintech I consulted for had a switch statement with 14 cases, 6 of which fell through intentionally, none of which had comments. The bug that cost them roughly $340k in a weekend of incident response traced back to a case someone added that fell through into a money-movement path. One missing break. GCC 7+ gives you -Wimplicit-fallthrough. It was off. Always turn it on.

for (int i = 0; i < n; i++)
for (int j = 0; j < m; j++)
for (int k = 0; k < p; k++)
for (int l = 0; l < q; l++) // l looks like 1 in every fontl as a variable name should be a compiler warning. Seriously. Name it depth, channel, row, something. The 4th loop variable almost always maps to something real in the domain. Say what it is.
Returns 0 on success? Or returns 0 on failure like strcmp? Returns -1 and sets errno? Returns a positive error code directly? All four conventions exist in the same codebase at some companies. I've seen it. The fix is one RETURNS comment at the top of every function. That's it. Three words and a value.
One .h file, 6,000 lines, everything included everywhere. Compile times spiral. Circular dependencies emerge. Nobody knows what depends on what anymore. The include graph becomes a directed graph with cycles. At that point you're not writing C, you're doing archaeology. Forward declarations exist. Use them. Keep headers minimal. Your CI pipeline will thank you in wall-clock minutes.
If I were starting a greenfield C project today I'd wire up clang-tidy with a custom .clang-tidy config on day one, enforce -Wall -Wextra -Werror -Wimplicit-fallthrough from the first commit, and write a one-page conventions doc that answers exactly two questions: what does a zero return mean, and when do we use macros vs inline functions. That doc would've saved the fintech $340k. It takes an afternoon to write.