I know that C was designed a long time ago when there wasn't much knowledge about language design. I'm not trying to berate its original creators, I just think it's time to move on to more modern systems programming languages like Nim, Rust or Zig. That's why this criticism is from a modern perspective.
I found this article which explains many things much better than I, so please check it out. My article contains a few duplicates and some additional points.
What's a better way of doing imports and macros than embedding another language with completely different syntax, which does naive text substitution, into your language? In order to avoid dangers which are not present in any sane import/macro systems, you have to do ugly hacks such as:
Include guards
#ifndef YOU_NEED_TO_MANUALLY_MAKE_SURE_THAT_YOUR_FILE_ISNT_INCLUDED_MULTIPLE_TIMES
#define YOU_NEED_TO_MANUALLY_MAKE_SURE_THAT_YOUR_FILE_ISNT_INCLUDED_MULTIPLE_TIMES
// your code here
#endif
The do { ... } while (0)
thing to make sure that a macro can be used as a normal function
Wrapping everything in parentheses to make a macro work like a normal function
#define MAX(a, b) ((a) > (b) ? (a) : (b))
If any parentheses are left out, it results in counter-intuitive behavior. Actually, even with them it doesn't work like a normal function: the arguments will be evaluated as many times as they're in the macro body!
Since many other languages (Java, C#, JavaScript, ...) have mindlessly copied most syntax from C, these mistakes have a profound effect on a whole family of languages.
KEYWORD (SOMETHING) STATEMENT
. Except for the do-while loop, which for some reason has the form KEYWORD STATEMENT KEYWORD (SOMETHING);
. Totally different and including an extra semicolon.
for (INIT; CONDITION; STEP) STATEMENT
rather than for (INIT; CONDITION) STATEMENT (STEP);
? By the same logic, we should visually indicate that the step (usually increment) is first executed after the statement.if (launch_button_pressed)
check(missile); /* this was added in after we decided to make our missile system safer */
launch(missile);
for item in items:
.label (NAME) STATEMENT
and case (VALUE) STATEMENT
.n++ * ++n
, the compiler doesn't stop you or even warn you. Instead, it's allowed to do whatever it wants — and in practice, it will probably just interpret the code in an arbitrary way and silently move on. And what's worse, even some things that aren't stupid fall under the category of undefined behavior — such as creating a function whose name starts with to
! That is, if you write a function top_results()
, the compiled program can format your hard drive.-Wall
enables only some warnings. What the fuck?a.out
, instead of being based on the source filename. So you need to do things like gcc my_awesome_program.c -o my_awesome_program
.