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
> 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.
If at least C solutions took advantage of abstract data types as advocated by modular design approaches before OOP took off, but no it is all reaching out to field data directly with macros, and clever pointer tricks that fail down.
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"
> If at least C solutions took advantage of abstract data types as advocated by modular design approaches
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.
Nope, you are just generalizing your opinion which is not quite true. My (and my colleagues) experience studying/programming C/C++ from the beginning-90's has been pretty good.
When the PC explosion happened, a lot of programmers without any CS background started with C programming and hence of course there is a lot of code (usually not long lasting) which do not adhere to software engineering principles. But quite a lot more C code was written in a pretty good style which was what one picked up at work if not already exposed to them during studies.
I still remember the books from late-80's/early-90's on the PC side, by authors like Al Stevens (utils/guis/apps using Turbo C) who wrote for Dr. Dobb's Journal. On the Unix side, of course you had Richard Stevens, P.J.Plauger, Thomas Plum etc. They all taught good C programming principles which are still relevant and practiced today.
Unfortunately your insightful comment is 30 years too late. You'll have to find a time-machine and go back to the 1990s and tell GNU/GTK/Gnome/etc that they are doing it wrong.
> 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."
True, it doesn't give you the bare machine. What it gives you is the thinnest of machine abstraction with the possibility of linking to your own assembly if you have the demand for it.
I am curious, what was it I said that you consider to be a myth? If I have some misunderstanding I would like to know. I looked at JOVIAL on wikipedia quickly but I can't see exactly how it would be thinner than C or if it's compiler would output something vastly different to a C compiler. Or did you mean it's as thin as C but it came out earlier?
> 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)
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.
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.
> OOP is non-scientific and the snake-oil hype ... ridding the world of the OOP rot is a far deeper infection.
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.
> 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.
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.
If software development taught me anything it is that everything that can go wrong will go wrong, the impossible will happen. As a result I prefer having less things that can go wrong in the first place.
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.
This is, perhaps surprisingly, what I consider the strength of C. It doesn't hide the issues behind some language abstraction, you are in full control of what the machine does. The bug is right there in front of you if you are able to spot it (given it's not hiding away in some 3rd party library of course) which of course takes many years of practice but once you have your own best practices nailed down this doesn't happen as often as you might expect.
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.
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.
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
[0] https://github.com/GStreamer/gst-plugins-good/blob/master/sy...
> 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.
[delayed]
If at least C solutions took advantage of abstract data types as advocated by modular design approaches before OOP took off, but no it is all reaching out to field data directly with macros, and clever pointer tricks that fail down.
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"
https://dl.acm.org/doi/10.1145/382284.382285
> If at least C solutions took advantage of abstract data types as advocated by modular design approaches
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.
Unfortunely I have seen plenty of counter examples since 1991.
Starting with RatC from "Book on C", 1988 edition, over to Turbo C 2.0 in 1991, all the way to modern times.
That is just not how most C codebases look like.
Nope, you are just generalizing your opinion which is not quite true. My (and my colleagues) experience studying/programming C/C++ from the beginning-90's has been pretty good.
When the PC explosion happened, a lot of programmers without any CS background started with C programming and hence of course there is a lot of code (usually not long lasting) which do not adhere to software engineering principles. But quite a lot more C code was written in a pretty good style which was what one picked up at work if not already exposed to them during studies.
I still remember the books from late-80's/early-90's on the PC side, by authors like Al Stevens (utils/guis/apps using Turbo C) who wrote for Dr. Dobb's Journal. On the Unix side, of course you had Richard Stevens, P.J.Plauger, Thomas Plum etc. They all taught good C programming principles which are still relevant and practiced today.
Unfortunately your insightful comment is 30 years too late. You'll have to find a time-machine and go back to the 1990s and tell GNU/GTK/Gnome/etc that they are doing it wrong.
> 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."
Right.
The article is well written and the author has touched upon all the points which make C still attractive today.
> The language shows you the machine, a machine which is not forgiving to mistakes.
Assembly does that, C not really, it is a myth that it does.
True, it doesn't give you the bare machine. What it gives you is the thinnest of machine abstraction with the possibility of linking to your own assembly if you have the demand for it.
Yet another myth, plenty of languages since JOVIAL in 1958 offer similar capabilities.
I am curious, what was it I said that you consider to be a myth? If I have some misunderstanding I would like to know. I looked at JOVIAL on wikipedia quickly but I can't see exactly how it would be thinner than C or if it's compiler would output something vastly different to a C compiler. Or did you mean it's as thin as C but it came out earlier?
Ok, if you insist on ultra precise description - "C is is the lowest level language among widely used".
> 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)
Agreed.
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.
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.
> OOP is non-scientific and the snake-oil hype ... ridding the world of the OOP rot is a far deeper infection.
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.
> 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.
Like setjmp()/longjmp() and signal(), very explicit. /s
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.
Enjoy!
If software development taught me anything it is that everything that can go wrong will go wrong, the impossible will happen. As a result I prefer having less things that can go wrong in the first place.
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.
This is, perhaps surprisingly, what I consider the strength of C. It doesn't hide the issues behind some language abstraction, you are in full control of what the machine does. The bug is right there in front of you if you are able to spot it (given it's not hiding away in some 3rd party library of course) which of course takes many years of practice but once you have your own best practices nailed down this doesn't happen as often as you might expect.
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.
How is one in full control of SIMD and CPU/OS scheduling in NUMA architecures in C?
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.