C sounds nice if your task is simple enough, or at least if you can decompose this to a series of loosely-connected simple-enough tasks.
But sometimes, there is an inherent complexity in what you are trying to implement, then C becomes way, way complex than C++. You build stuff, and there is so many manual steps, and none of them must be missing, or things will subtly break.
A good example is "gstreamer", the multimedia streaming framework. It is implemented in pure C. It needs to use basic data structures, so it uses GLib. It also need to support runtime-defined connection graph, so it is built on top of GObject.
Yes, build times are amazingly fast. But you pay for it - look at something simple, like display_name property[0]. There is display_name member, and PROP_DISPLAY_NAME enum, and switch case in setter (don't forget to free previous value!) , and switch case in getter, and it's manually installed in class_init, and you need to manually free it in dispose (except they forgot this).
So many places just for a single property, something that would have been 1 or 2 lines in a well-structured C++ app. And unlike C++, you cannot make simple rules like "never use char* except for 3rd party libs" - it's all those multi-point checklists which are not even written down. A
> You build stuff, and there is so many manual steps
"The real goal isn’t to write C once for a one-off project. It’s to write it for decades. To build up a personal ecosystem of practices, libraries, conventions, and tooling that compound over time."
> It needs to use basic data structures, so it uses GLib. It also need to support runtime-defined connection graph, so it is built on top of GObject.
That's running into the age old trap of trying to shoehorn an OOP system into C, just don't do that ;) E.g. don't design your systems around the OOP paradigm in the first place.
People point to GObject say it's complicated and compare it to C++ classes. But the same thing as C++ classes in C would also be far simpler. GObject is so complicated, because it is essentially runtime creation and modification of classes (not objects, classes). Doing that in C++ will also be some work and look ridiculously complicated.
> Code gets simpler because it has to, and architecture becomes explicit.
> The real goal isn’t to write C once for a one-off project. It’s to write it for decades. To build up a personal ecosystem of practices, libraries, conventions, and tooling that compound over time. Each project gets easier not because I've memorized more tricks, but because you’ve invested in myself and my tools.
I deeply appreciate this in the C code bases I work in (scientific computing, small team)
First off, I want to congratulate you on reaching this milestone. I think this is the state where the most seasoned programmers end up. They know how to write code that works and they don't need a language to "help" or "guide" them.
> In C, you can see what the machine is doing. Allocations don’t hide behind constructors, and destructors don’t quietly run during stack unwinding. You can profile at the machine-code level without feeling like you’re peeling an onion, appropriately shedding tears the whole time.
This is why explicit control flow is important design goal for systems programming language. This is basically 2/3 of core design principles in Zig.
I did a lot of c++ in the mid-90s, often on teams with experienced C programmers new to C++.
They had little appetite for C++, it was 90% mgmt saying ‘use the shiny new thing we read about’. I was the FNG who ‘helped’ them get thru it by showing them the tools & lingo that would satisfy mgmt.
OOP is non-scientific and the snake-oil hype made it cancerous. C++ has ballooned into an absurd caricature. It obfuscates business logic with crypto-like strength, it doesn’t clarify anything. I feel like a war criminal. Replacing C++ is one thing but ridding the world of the OOP rot is a far deeper infection.
I later spent years doing my Typhoid Mary bit in the Java community before abandoning the heresy. Repent and sin no more, that’s all one can do.
Good Article. The author has touched upon all the points that make C still attractive today.
A few more points;
C allows you program everything from dinky little MCUs all the way to honking big servers and everything in-between. It also allows you to span all levels of programming from bare-metal, system-level (OS/System utilities etc.) to any type of applications.
There has also been a lot of work done and available on Formal Verification of C programs Eg. Frama-C, CBMC etc.
Finally, today all LLM agents are well trained on the massive publicly available C codebases making their output far more reliable.
PS: See also Fluent C: Principles, Practices, and Patterns by Christopher Preschern for further study.
I wound up going back to C in a big way about five years ago when I embarked on Scheme for Max, an extension to Max/MSP that lets you use s7 Scheme in the Max environment. Max has a C SDK, and s7 is written in 100% ANSI C. (Max also has C++ SKD, but is far less comprehensive with far fewer examples and docs).
I was, coming from being mostly a highlevel language coder, suprised at how much I like working in this combo.
Low level stuff -> raw C. High level stuff -> Scheme, but written such that I can drop into C or move functions into C very easily. (The s7 FFI is dead simple).
It's just really nice in ways that are hard to articulate. They are both so minimal that I know what's going on all the time. I now use the combo in other places too (ie WASM). It really forces one to think about architecture in what I think is a good way. YMMV of course!
Returning to C after a decade away, I think the bottom line is that the reason why C has stuck around for so long is straight up path dependence. C is the foundation of Unix, and Unix-like operating systems took over the world, and C is a decent enough systems programming language that people started using it to write other OSes as well.
It bothers me that there’s some kind of mysticism around C. I keep seeing weird superstitions like, “modern processors are designed to run C code fast”. Or that there’s some inherent property of C that makes it “closer to the hardware”. The reality is just that C has been around for so long that there are millions of lines of optimizations handcrafted into all the compilers, and people continue to improve the compilers because there are billions of lines of C that will benefit from it.
FORTRAN is also crazy fast, but people don’t worship it the same way. SBCL and even some BASIC compilers approach the speed of C. And C is a high level language, despite what many people who have never touched assembler may assert.
C is not a bad language, and once you get your head around it you can write anything in C, but it’s absolutely full of landmines (sorry, “undefined behaviors”).
The author makes some really great points about the standard library. A lot of C’s pain points stem from memory management, string handling, etc. which stem from quirks in the standard library. And yet it’s possible to completely ignore the standard library, especially if you’re on an embedded system or bare metal. Personally I feel that a large standard library is a liability, and a much stronger property of a language is that the base language is small enough to keep the entirety of it in your head and still have the ability to implement any algorithm you need in a minimal number of lines without resorting to mental gymnastics, and still be able to read the code afterwards. I think this is why Lisp is so revered. I feel like Lua also falls into this bucket.
We need to stop starting flame wars about which language is best, cargo culting the newest shiny language, and instead just learn the strengths and weaknesses of our tools and pick the right tool for the job. You can machine almost anything on a lathe, but sometimes a drill press or a mill are much better suited for the task.
I program in C and I like many of the reasons they mention here, are things I like about C programming. They use C89 (and sometimes C99), although I do use some of the GNU extensions (which, as far as I know, both GCC and Clang support them).
I don't think most of the recent essays we're seeing defending C are coming from experienced C veterans, but instead from much younger programmers who were introduced to "The C Way" of doing things by Handmade Hero.
It's surprising the number of people for whom that series appears to have completely rewritten their understanding of programming. It's almost like when someone reads Karl Marx or Richard Dawkins for the first time and finds themselves questioning everything they thought they knew about the world. It's such an outsized impact for such a seemingly straightforward tutorial series.
I'm perfectly happy writing C With Classes. There isn't a problem in my domain that can't be solved with C, and frothing at the mouth about "safety" simply isn't relevant. I program machines and I need a language that doesn't do everything possible to hide that fact.
C is a fine language. Sure it's got sharp edges to poke your eyes our and big gears that will rip your arm off, but guess what? So does the machine. Programming a machine is an inherently unsafe activity and you have to act like a responsible adult, not some cargo culting lunatic that wants to purge the world of all code written before 2010.
I'm going back to statically allocating my 2KB of SRAM now. Humbug, etc
17 comments
[ 1.1 ms ] story [ 38.8 ms ] threadBut sometimes, there is an inherent complexity in what you are trying to implement, then C becomes way, way complex than C++. You build stuff, and there is so many manual steps, and none of them must be missing, or things will subtly break.
A good example is "gstreamer", the multimedia streaming framework. It is implemented in pure C. It needs to use basic data structures, so it uses GLib. It also need to support runtime-defined connection graph, so it is built on top of GObject.
Yes, build times are amazingly fast. But you pay for it - look at something simple, like display_name property[0]. There is display_name member, and PROP_DISPLAY_NAME enum, and switch case in setter (don't forget to free previous value!) , and switch case in getter, and it's manually installed in class_init, and you need to manually free it in dispose (except they forgot this).
So many places just for a single property, something that would have been 1 or 2 lines in a well-structured C++ app. And unlike C++, you cannot make simple rules like "never use char* except for 3rd party libs" - it's all those multi-point checklists which are not even written down. A
[0] https://github.com/GStreamer/gst-plugins-good/blob/master/sy...
"The real goal isn’t to write C once for a one-off project. It’s to write it for decades. To build up a personal ecosystem of practices, libraries, conventions, and tooling that compound over time."
That's running into the age old trap of trying to shoehorn an OOP system into C, just don't do that ;) E.g. don't design your systems around the OOP paradigm in the first place.
> The real goal isn’t to write C once for a one-off project. It’s to write it for decades. To build up a personal ecosystem of practices, libraries, conventions, and tooling that compound over time. Each project gets easier not because I've memorized more tricks, but because you’ve invested in myself and my tools.
I deeply appreciate this in the C code bases I work in (scientific computing, small team)
Enjoy!
Assembly does that, C not really, it is a myth that it does.
This is why explicit control flow is important design goal for systems programming language. This is basically 2/3 of core design principles in Zig.
They had little appetite for C++, it was 90% mgmt saying ‘use the shiny new thing we read about’. I was the FNG who ‘helped’ them get thru it by showing them the tools & lingo that would satisfy mgmt.
OOP is non-scientific and the snake-oil hype made it cancerous. C++ has ballooned into an absurd caricature. It obfuscates business logic with crypto-like strength, it doesn’t clarify anything. I feel like a war criminal. Replacing C++ is one thing but ridding the world of the OOP rot is a far deeper infection.
I later spent years doing my Typhoid Mary bit in the Java community before abandoning the heresy. Repent and sin no more, that’s all one can do.
A few more points;
C allows you program everything from dinky little MCUs all the way to honking big servers and everything in-between. It also allows you to span all levels of programming from bare-metal, system-level (OS/System utilities etc.) to any type of applications.
There has also been a lot of work done and available on Formal Verification of C programs Eg. Frama-C, CBMC etc.
Finally, today all LLM agents are well trained on the massive publicly available C codebases making their output far more reliable.
PS: See also Fluent C: Principles, Practices, and Patterns by Christopher Preschern for further study.
I was, coming from being mostly a highlevel language coder, suprised at how much I like working in this combo.
Low level stuff -> raw C. High level stuff -> Scheme, but written such that I can drop into C or move functions into C very easily. (The s7 FFI is dead simple).
It's just really nice in ways that are hard to articulate. They are both so minimal that I know what's going on all the time. I now use the combo in other places too (ie WASM). It really forces one to think about architecture in what I think is a good way. YMMV of course!
Reminds me of optimizations done in the early days of Erlang and BEAM using C for performance reasons - https://www.erlang.org/blog/beam-compiler-history/
It bothers me that there’s some kind of mysticism around C. I keep seeing weird superstitions like, “modern processors are designed to run C code fast”. Or that there’s some inherent property of C that makes it “closer to the hardware”. The reality is just that C has been around for so long that there are millions of lines of optimizations handcrafted into all the compilers, and people continue to improve the compilers because there are billions of lines of C that will benefit from it.
FORTRAN is also crazy fast, but people don’t worship it the same way. SBCL and even some BASIC compilers approach the speed of C. And C is a high level language, despite what many people who have never touched assembler may assert.
C is not a bad language, and once you get your head around it you can write anything in C, but it’s absolutely full of landmines (sorry, “undefined behaviors”).
The author makes some really great points about the standard library. A lot of C’s pain points stem from memory management, string handling, etc. which stem from quirks in the standard library. And yet it’s possible to completely ignore the standard library, especially if you’re on an embedded system or bare metal. Personally I feel that a large standard library is a liability, and a much stronger property of a language is that the base language is small enough to keep the entirety of it in your head and still have the ability to implement any algorithm you need in a minimal number of lines without resorting to mental gymnastics, and still be able to read the code afterwards. I think this is why Lisp is so revered. I feel like Lua also falls into this bucket.
We need to stop starting flame wars about which language is best, cargo culting the newest shiny language, and instead just learn the strengths and weaknesses of our tools and pick the right tool for the job. You can machine almost anything on a lathe, but sometimes a drill press or a mill are much better suited for the task.
It's surprising the number of people for whom that series appears to have completely rewritten their understanding of programming. It's almost like when someone reads Karl Marx or Richard Dawkins for the first time and finds themselves questioning everything they thought they knew about the world. It's such an outsized impact for such a seemingly straightforward tutorial series.
C is a fine language. Sure it's got sharp edges to poke your eyes our and big gears that will rip your arm off, but guess what? So does the machine. Programming a machine is an inherently unsafe activity and you have to act like a responsible adult, not some cargo culting lunatic that wants to purge the world of all code written before 2010.
I'm going back to statically allocating my 2KB of SRAM now. Humbug, etc