Recently, I've been growing disinterested in other programming languages once more. While I do have a lot of admiration and love for Scala and an appreciation of languages like Odin, they do not instill joy in me the way C does.
There is something special to me about the janky, artificial "low-level" feeling of C. I specify "artificial" as most usage of C lives within what can be best described as it's "PDP-11 box", where the kernel and CPU itself acts as a fast PDP-11 for C to run on. While it is arguable about whether or not this abstraction "is" the computer or not, the distinction is irrelevant to this article.
So in this article, I just want to write a little love letter to the bizarre idiosyncracies of C. This will cover mostly ANSI C and ISO C99, as I largely avoid modern C standards.
While C does have static typing, to call it strong typing would be laughably wrong. Between blind casting of pointers and the C compiler yielding to the programmer without complaint, C feels like you're steering the computer itself. You can easily tell the C compiler you know better, which can result in some very intriguing (as well as very dangerous) results.
What if I have a block of data, and I want to pull the header from the top of the buffer. In other languages, there is a requirement to marshall between the types, including explicit coercion in some cases to allow the type system to maintain correctness. In C, you just...
struct header __attribute__((packed)) { /* Header fields... */ } struct header header = ((struct header *)data_buffer)[0];
No complex marshalling, C (and you, in this case) don't even care if the data buffer is long enough. That's a job for somewhere before blindly pulling the header from the raw buffer.
The level of flexibility C gives you is genuinely unparalled. Does it make it better? Very, very arguable. But do say C is not flexible is a lie.
Most C compilers contain their own inline assembly utilities. This is extremely handy when you need to manipulate the ABI directly. Even without inline assembly, "setjmp" and "longjmp" are enough to irrecoverably destroy C's calling convention.
I learned a lot about how to manipulate the stack specifically when working on my own stackful coroutine library: libjco. With the knowledge of how the C calling convention works, you can directly interact with the stack and even outright replace the stack (much to Valgrind's dismay). With that power, you can essentially rewrite the applications control flow at runtime, at will. This kind of capability is quite rare across programming languages. I assume LISPs have mechanisms to do so (and probably in safer ways), but saying LISP can do something isn't controversial.
I do love C, I cannot help it. I know Rust has better typing and compile-time structures. I know Haskell and Scala provide the type systems necessary to write highly correct code. But none of those languages make me enjoy the act of programming, they instead turn it into something akin to data entry. Instead of controlling the compiler and the system, you control neither. You only control the abstract logic flow that will eventually be ran on the system. And to me, there is no joy in that.
=> libjco
=> Back to Index This content has been proxied by September (ba2dc).Proxy Information
text/gemini