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
[0] https://github.com/GStreamer/gst-plugins-good/blob/master/sy...
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.
There are several books on the matter, that obviously very few read.
Here one paper example from 1985 on the subject, "Modular programming in C: an approach and an example"
People have been writing C code with ADTs and "Modules" from the very beginning.
Two excellent examples which come to mind are; Andrew Tanenbaum's Minix book Operating Systems Design and Implementation and David Hanson's C Interfaces and Implementations: Techniques for Creating Reusable Software.
And of course the Linux Kernel is full of great modular C techniques which one can study.
ximagesrc display_name=:1 ! video/x-raw,framerate=20/1 ! videoscale ! videoconvert ! x264enc tune=zerolatency bitrate=500 speed-preset=superfast ! rtph264pay ! udpsink host=127.0.0.1 port=5000
and it automatically loads .so files, creates all those components and connects them to each other. Super handy for all sorts of fun audio/video processing.
So all that C ceremony is required because user _should_ be able to say "ximagesrc display_name=:1", and possibly dynamically change this attribute to something else via script command (because a lot of time gstreamer is embedded in other apps).
So if you know how to achieve the same without trying to "shoehorn an OOP system into C", do let me know. But I bet whatever solution you came up with would be very close to what GStreamer ended up doing, if not even more complex.
(Unless what you are trying to say is: "If problem's most-efficient representation is OOP-like, don't use it with C because C is for simpler problems only. Use complex languages for complex tasks." If that's the case, I fully agree)
I created a quite similar OOP system for C around 1995 (as I guess did most programmers at that time who were fascinated by Objective-C), classes were implemented in DLLs and were loaded on demand, classes were objects themselves, the tree of class objects could be inspected (e.g. runtime-type-information and -reflection), and the whole system was serializable - this was for a PC game (https://en.wikipedia.org/wiki/Urban_Assault).
It looked like a neat thing at the time, but nothing a couple of structs with function pointers or a switch-case message dispatcher wouldn't be able to do just as well, definitely not something that should be the base for more than one product, and most definitely nothing that should have survived the OOP hype of the 90's ;)
You can do it, but you will have to either repeat yourself at least a little, use some very ugly macros, or use a code generator.
And many of those ugly macro tricks work in C as well. So do code generators.
That said, as C++ has added features, this type of metaprogramming has gotten easier and easier, and more and more distinct from C. This culminates in C++26 reflection which will finally make it possible to just define a struct and then automatically generate serialization/deserialization for it, without hacks. Once reflection is implemented and widely adopted, then I will agree with you that this should be 1 or 2 lines in a well-structured C++ app.
The only way to work around is immediate mode UI, but this requires fairly powerful CPU, so it's only feasible on the modern machines. Certainly not something that people would want about 30 years ago, they still cared about performance back then.
"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."
This basically requires one to be working solo, neither receiving not sharing the source code with others, treating third-party libraries as blackboxes.
I guess this can work for some people, but I don't think it would work for everyone.
No. It's that you've built up a personal database of libraries, best-practices, idioms, et al. over decades.
When you move on to a new project, this personal database comes with you. You don't need to wonder if version X of Y framework or library has suddenly changed and then spend a ton of time studying its differences.
Of course, the response to this is: "You can do this in any language!"
And you'd be right, but after 20 years straight of working in C alongside teams working in Java, Perl, Python, Scheme, OCaml, and more, I've only ever seen experienced C programmers hold on to this kind of digital scrapbook.
Tbh, this is an intriguing idea. Determine the size of a library (or module in a bigger system) by what one programmer can manage to build and maintain. Let the public interface be the defining feature of a library, not its implementation.
The article is well written and the author has touched upon all the points which make C still attractive today.
> 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)
I generally try to use C++ as a "better C" before the design complexity makes me model higher-level abstractions "the C++ way". All abstractions have a cognitive cost and C makes it simpler and explicit.
Assembly does that, C not really, it is a myth that it does.
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.
Enjoy!
Since I acknowledge my own fallibility and remote possibilities of bad things happening I have come to prefer reliability above everything else. I don't want a bucket that leaks from a thousand holes. I want the leaks to be visible and in places I am aware of and where I can find and fix them easily. I am unable to write C code to that standard in an economical fashion, which is why I avoid C as much as possible.
Also, code doesn't need to be bulletproof. When you design your program you also design a scope saying this program will only work given these conditions. Programs that misbehaves outside of your scope is actually totally fine.
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/
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.
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
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.
You are spewing nonsense.
Read Bertrand Meyer's Object-Oriented Software Construction, Barbara Liskov's Program Development in Java: Abstraction, Specification, and Object-Oriented Design and Brad Cox's Object-Oriented Programming: An Evolutionary Approach for edification on OOD/OOP.
> Brad Cox's Object-Oriented Programming: An Evolutionary Approach
i liked this one and got some good insights from it, though it was so old it was hard to get through...the snake-oil aspect though, i think is true to a large extent:
oop became a huge hype and a marketing term, and things like c++ and java oop are so far away from the original ideas of the original 'oop' of smalltalk and we have been suffering from really bad/low quality abstractions (javas infamous FactoryFactory pattern, subclass everything etc) for a long time...
This is the fundamental misunderstanding; there is no "original OOP" but different "strains of OOP" viz. the Simula67 vs. Smalltalk models.
C++ followed the Simula approach (i.e. static object model) while Java is hybrid mixing both Simula and Smalltalk approaches (i.e. dynamic object model but with static typing). You have to look at the entirety of the OOD/OOP domain to understand how modern languages have evolved OOP support.
See also OO History: Simula and Smalltalk - https://www.cs.cmu.edu/~charlie/courses/15-214/2014-fall/sli...
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.