daru.se

draft 2024-02-24

C programming language? Really?

C is obviously bad. Try comparing two strings, and oh my god the security vulnerabilities! Manual memory management! The god damned C standard lib!?

What if a few fresh (or perhaps very old) ideas makes the C programming language THE default language for most tasks?

What if in the same way Procedural Programming opposed to Object Oriented Programming or Functional Programming is actually THE paradigm?

What if we could actually move BACK towards simplicity?

What if?

Following are ideas I've picked up from various sources when I finally got sick and tired of not understanding how my programs works. I still fully don't and probably never will. But since about a year ago when I started this journey I've learned so much it's almost ridiculous!

1. The Arena Allocator

Also called linear allocator or stack allocator.

The basic idea is that any function that needs to allocate dynamic memory takes an Arena as one of it's arguments.

So the arena is created/initialized at some point in the program. It's then passed around throughout a one or more functions where each function allocates the memory it needs to accomplish some task.

But each caller to a function is not responsible for freeing the memory the function has allocated! Instead we free all the memory in the Arena in one sinle call when we don't need any of the objects allocated anymore. The lifetime of the objects (objects, with an s) has ended.

Then we can start allocating from the start of the Arena memory again. This may sound weird at first. But it's surprisingly useful, powerful and massively simplifies memory management!

As an added bonus it's radically much faster than the traditional malloc/free!

Resources:

2. String struct instead of null-terminated c-strings

Null-terminated C-strings is in this day and age a bad idea. Probably made sense back in the day. But it is trivial to have length based Strings if combined with the Arena allocator!

3. Base layer and your own "standard lib"

Now with a much simpler way to reason about memory allocations and lifetimes using Arenas we can build our own "standard library".
I have written one for myself that handles common String operations, type conversions (casts) and helpful things like timestamps and such.
It's a bit of work (once?) sure, but I get angry with rage every time I have to decrypt some old legacy libc moronic function name or parameters! Truly it's worth hours to just re-implement

atoi(const char * str)
and such cryptic shit to something understandable such as
int32_from_string(String *number)

4. Use a debugger

When John Carmack speaks one better listen! And now that I've finally learned to use a debugger it's insanity not to. Print statements is very slow and cumbersome in comparison. Granted, the debugger situation on Linux is bad but gf2 is at least less terrible than the default GDB frontend.

And if get The RAD Debugger also on Linux as promised it will be a game changer!

5. Static analysis

Even though the Arena allocator makes it much simpler to reason about memory and lifetimes we may still fuck things up. So all my debug builds uses static analysis.

-fsanitize=address,undefined
Sometimes I also run things through Valgrind. I'm not super worried about memory leaks up until it's time to actually deploy something to production.
At this point I suspect I spend less time fixing some memory leak than times I would have spent fighting Rust's borrow checker.

6. Non-generic. Yes, be specific!

Generic cross platform works for everybody and any inconceivable use case: this just does not work! Be specific and solve the damn problem. And we may have good maintainable software.

7. Single compilation unit builds and simple build scripts

I use single compilation unit builds. Meaning from the compilers point of view it's just one big .c file it compiles. This keeps builds very fast.
And since builds are fast and we can just rebuild everything every time there is no need for build tools besides a simple shell script to build! It's simple, fast and beautiful!

What about Rust, Modern C++, Golang, Zig, Odin and all the other C killers?

Well they are either very complicated, not production ready (another 10 years?) and/or vulnerable to yet another corporate decision that will fuck everything up. Rust foundation controversy and Golang telemetry fiasco.

On Linux debugger tools are already bad enough and with any other language than C it's just forget about it. Crashing, buggy and other weirdness all the way.

I'm not saying C is perfect in any way. Just that these other languages tend to solve some of C problems by introducing a ton of complexity in other ends. And it takes years, decades to get it right anyway.