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.
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.
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 know try and use the combo in other places too (ie WASM).
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.
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)
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.
Good luck making any sort of UI without OOP-like methods. The moment you have grouped state (say "button_enabled", "button_shown", and "button_checked") and corresponding methods, you get something OOP-like.
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.
> 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."
You mean, you are not worried about high complexity of codebase because you work with it every day for decades, so you know all this complexity by heart?
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.
I'm being pedantic, but on modern hardware, the ISA is an abstraction over microarchitecture and microcode. It's no longer a 1-to-1 representation of hardware execution. But, as programmers, it's as low as we can go, so the distinction is academic.
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.
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.
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.
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 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.
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 know try and use the combo in other places too (ie WASM).
reply