None of the alternatives have stability. What was exemplary & idiomatic rust pre-pandemic would be churlish & rejected now and the toolchain use would be different anyway.
Carpenters, plumbers, masons & electricians work on houses 3-300 yrs old, navigate the range of legacy styles & tech they encounter, and predictably get good outcomes.
Only C has, yet, given use that level of serviceability. C99, baby, why pay more?
When there’s an alternative that can compete with that sort of real-world durability, C will get displaced.
cannonpr 1 days ago [-]
Having just finished renovating a 140-year-old home with solid brick walls that was slowly collapsing and deteriorating due to the aforementioned professionals’ application of modern and chemically incompatible materials to it… I’m not sure I agree. It’s also why a lot of the UK’s building stock is slowly rotting with black mould right now. Literally none of the professionals I hired, before I trained them, knew how to properly repair a type of home that represents 30% of the UK building stock.
yeame 17 hours ago [-]
[flagged]
pjmlp 1 days ago [-]
Outside UNIX clones, and embedded space where it is mostly a religious point of view than available compiler toolchains, C has already been displaced.
Even the most relevant C compilers are no longer written in C.
kreco 1 days ago [-]
> Even the most relevant C compilers are no longer written in C.
Worth to point out that most of the C compilers are also C++ compilers.
So the point is kind of distorted.
pjmlp 1 days ago [-]
On the contrary, GNU Compiler Collection is written in C++ and happens to have C as one of the supported fronteds, having moved away from C in 2008.
LLVM is written in C++, also supports multiple language frontends, one of them happens to be C.
Visual C++, also written in C++, happens to still compile some form of C, and after adding some C11 and C17 support (a few items are still missing in 2026), Microsoft is in no rush to add anything else.
The language that matters is the implementation, otherwise we can also talk about the relevance of PL/I compilers from IBM and Unisys, written in C++.
ux266478 1 days ago [-]
> Only C has, yet, given use that level of serviceability.
On the contrary, Lisp outshines C to a large degree here. Success has nothing to do with technical merit (if such a thing even exists), it's not a rational game.
Panzer04 1 days ago [-]
What makes you say that?
ux266478 1 days ago [-]
Reduce is a Lisp library that's still in active use from 1968, making it older than C itself. We can point to GNU Emacs as an ancient and venerable self-contained Lisp tortoise with more wrinkles than are finitely enumerable, and is in fact a hosted Lisp operating system. Pulling it apart and working with it is admittedly a treat even if I loathe it as a text editor. Mezzano is a modern Lisp OS that you can play with in a VM, and might give you an idea of why Lisp is such a great systems language.
In short: Lisp is semantic and geared towards a living system. The basic REPL is sh + cc + ld + db (and a few others) all in one. It's almost a little mind bending how nice these systems are put together, how cleanly they work. C is like pulling teeth in comparison.
I'm not even a fan of Lisp or sexpr languages. But it's the obvious heavyweight champion of longetivity and ultra-pragmatic service record... Yes, even in the systems domain.
jklowden 21 hours ago [-]
Not sure how viable Mezzano is. The most recent bug report was two years ago. The “beginnings of a manual” was last updated 7 years ago.
A better example might be Guix, depending how “operating system” is defined.
ux266478 19 hours ago [-]
Both of which are besides viability. It's just a usable system that gets you an idea of how an OS works when it's Lisp all the way down. It didn't invent this idea, it's just a modern example of it.
thayne 1 days ago [-]
FWIW, the crabi project within rust is trying to improve on some parts of it. But it is still built on (a subset of) the environments c ABI. And it doesn't fix all the problems.
okanat 1 days ago [-]
The replacement has already happened. It is HTTP and JSON for 99% of the software developed today. The reason C stayed has multiple reasons but most obvious ones are for me are:
- People just stopped caring about operating systems research and systems programming after ~2005. Actual engineering implementations of the concepts largely stopped after the second half of 90s. Most developers moved on to making websites or applications in higher level programming languages.
- C hit a perfect balance of being a small enough language to grok, being indepedent of the system manufacturers, reflecting the computer architecture of 80s, actually small in syntax and code length and quite easy to implement compilers for. This caused lots of legacy software being built into the infrastructure that gave birth to the current contemporary popular OSes and more importantly the infrastructure of the Internet. Add in .com bubbles and other crises, we basically have/had zero economic incentive to replace those systems.
- Culture changed. We cared more about stability, repairability and reusability. Computers were expensive. So are programmers and software. Now computers are cheap. Our culture is more consumerist than ever. The mentality of "move fast and break things" permeated so well with economic policy and the zeitgeist. With AI it will get worse. So trying to make a real alternative to C (as a generic low level OS protocol) has reduced cultural value / optics. It doesn't fill the CVs as well.
It doesn't mean that people haven't tried or even succeeded. Android was successful in multiple fronts in replacing C. Its "intents" and low level interface description language for hardware interfaces are great replacement for C ABI. Windows' COM is also a good replacement that gets rid of language dependence. There are still newer OSes try like Redox or Fuchsia.
noosphr 1 days ago [-]
I'd never thought I'd see the day that anyone praises COM.
tonyedgecombe 1 days ago [-]
If you read Don Box’s book on COM he goes through every decision they made and the rationale for it. It all seemed to make sense.
Unfortunately I think Don Box’s was the only person in the world who really understood it all.
pjmlp 1 days ago [-]
As idea it is great, as the tooling available in C++ Builder, Delphi, VB 6, C++/CX (WinRT is basically COM with extras) also great.
Using it from MFC, kind of alright.
Using it from .NET, depends if Framework, .NET Native, or modern, with various levels of usability.
Using it from ATL, WRL, C++/WinRT, is a mess unfortunely.
okanat 1 days ago [-]
Compared to wobbly types in C and hidden contracts due to C compiler internals, COM's IDL-based approach is much better!
PaulDavisThe1st 1 days ago [-]
> People just stopped caring about operating systems research and systems programming after ~2005.
and so it was that after that date, all development of
embedded systems
kernel drivers
digital audio workstations
video editors
codecs for audio and video
anything that involved actually controlling non-computer hardware
game engines
came to a grinding halt, and no further work was done.
okanat 1 days ago [-]
What I mean is all of those things are more of the same things we did since 90s.
It is better and higher performing hardware but until Rust and Zig arrived, the most popular ways of designing system-level software stayed the same. RTOSes work the same as how they work in late 90s / early 00s. C ABI is still the majority of communication interface. Interacting with OS using system calls stayed the same. After virtual memory and paging no big change in OS design happened. Main programming design patterns in C and C++ also stayed the same.
One area that stayed interesting is GPU programming. Nowadays CPUs basically provide us a PDP-11 simulator. Most of the time you don't need to recompile programs to harness most of the gains from a CPU. GPUs expose more of their internal hardware detaila than CPUs and unlike CPUs you need to recompile programs (which is what a GPU userspace driver does) to use newer models.
pjmlp 22 hours ago [-]
Nowadays, NVidia designs their GPUs for C++, not C.
And now with tiled architecture, Python JIT as well.
pjmlp 1 days ago [-]
On Windows, macOS and Android, most of that development on that list is done in C++, not C.
jacinabox 17 hours ago [-]
If you think about it, those who shape mainstream opinion on operating systems were always mostly bought off, and invested in the mainstream operating systems. That investment meant that a niche operating system like Plan9 or BeOS was never going to see wide support from the opinion shapers, even if those OSes had some interesting ideas.
sinnsro 1 days ago [-]
> It doesn't mean that people haven't tried or even succeeded. Android was successful in multiple fronts in replacing C. Its "intents" and low level interface description language for hardware interfaces are great replacement for C ABI. Windows' COM is also a good replacement that gets rid of language dependence. There are still newer OSes try like Redox or Fuchsia.
I am not sure I buy this from a system perspective, especially when taking this[1] into consideration.
Name me a stable binary interface that is not ints and arrays of ints
okanat 1 days ago [-]
C ABI is more than how ints and array of ints sit next to each other in memory.
It cares about calling conventions and what you can store in registers vs what you cannot. There are multiple possible ways of doing an RPC call and C ABI only provides one way of doing it.
kevin_thibedeau 21 hours ago [-]
How big is an int?
pjmlp 1 days ago [-]
COM, WinRT, XPC, AIDL.
Now you can move the goal posts and assert that any data serialized into a memory buffer is an array of ints.
chasil 1 days ago [-]
In pieces of my code, I need to call setuserid() to manage some of the security that I designed in 2010.
There was no Rust at that point, and I used the most basic tool that could do it.
Could I have done this in Java with gymnastics of JNI, linking C into the JRE?
Definite maybe.
pjmlp 1 days ago [-]
Yes, nowadays with Panama, and before Rust was around, JNA was already there so using JNI wasn't strictly necessary.
direwolf20 1 days ago [-]
JNA is a whole dependency of its own, shame it's not built into the JRE
pjmlp 23 hours ago [-]
It is nowadays, that is what Panama is for.
Unless one is stuck with Android Java, Google's J++.
kogasa240p 1 days ago [-]
>Culture changed. We cared more about stability, repairability and reusability. Computers were expensive. So are programmers and software. Now computers are cheap. Our culture is more consumerist than ever. The mentality of "move fast and break things" permeated so well with economic policy and the zeitgeist. With AI it will get worse. So trying to make a real alternative to C (as a generic low level OS protocol) has reduced cultural value / optics. It doesn't fill the CVs as well.
IMO I do see this changing in the future as higher power computers become expensive once again, and I'm not just referring to the recent chip shortage.
charleslmunger 1 days ago [-]
C99, but with a million macros backporting features from newer language versions and compiler extensions. Lovely features you don't get with ordinary c99:
free_sized
#embed
static_assert
Types for enum
Alignof, alignas, aligned_alloc
_Atomic
NuclearPM 22 hours ago [-]
Churlish can’t be the right word here.
pizlonator 1 days ago [-]
It’s weird how whiny this post is. Like there’s zero intellectual curiosity about why C got this way, and why C gets to be the foundation for how systems software is written.
I could write a whole essay about why, but now isn’t the time. I’m just going to enjoy the fact that TFA and the author don’t get it.
munificent 1 days ago [-]
> why C gets to be the foundation for how systems software is written.
Is there an answer here more interesting than "it's what Unix and Windows were written in, so that's how programs talked to the OS, and once you have an interface, it's impossible to change"?
jonjacky 1 days ago [-]
It wasn't a coincidence, or an accident. C was specifically designed to write Unix, by people who had experience with a lot of other computer languages, and had programmed other operating systems including Multics and some earlier versions of Unix. They knew exactly what they were doing, and exactly what they wanted.
munificent 1 days ago [-]
I'm not sure what you mean by "coincidence" or "accident" here.
C is a pretty OK language for writing an OS in the 70s. UNIX got popular for reasons I think largely orthogonal to being written in C. UNIX was one of the first operating systems that was widely licensed to universities. Students were obliged to learn C to work with it.
If the Macintosh OS had come out first and taken over the world, we'd probably all be programming in Object Pascal.
When everyone wanted to program for the web, we all learned JavaScript regardless of its merits or lack thereof.
I don't think there's much very interesting about C beyond the fact that it rode a platform's coattails to popularity. If there is something interesting about it that I'm missing, I'd definitely like to know.
jonjacky 1 days ago [-]
It is often said that C became popular just because Unix was popular, due to being free -- it just "rode its coattails" as you put it.
As if you could separate Unix from C. Without C there wouldn't have been any Unix to become popular, there wouldn't have been any coattails to ride.
C gave Unix some advantages that other operating systems of the 1970s and 80s didn't have:
Unix was ported to many different computers spanning a large range of cost and size, from microcomputers to mainframes.
In Unix both the operating system and the applications were written in the same language.
The original Unix and C developers wrote persuasive books that taught the C language and demonstrated how to do systems programming and application programming in C on Unix.
Unix wasn't the first operating system to be written in a high-level language. The Burroughs OS was written in Algol, Multics was written in PL/I, and much of VMS was written in BLISS. None of those languages became popular.
IN the 1970s and 80s, Unix wasn't universal in universities. Other operating systems were also widely used: Tenex, TOPS-10, and TOPS-20 on DEC-10s and 20s, VMS on VAXes. But their systems languages and programming cultures did not catch on in the same way as C and Unix.
The original Macintosh OS of the 1980s was no competitor to Unix. It was a single user system without integrated network support. Apple replaced the original Macintosh OS with a system based on a Unix.
pjmlp 1 days ago [-]
> Unix wasn't the first operating system to be written in a high-level language. The Burroughs OS was written in Algol, Multics was written in PL/I, and much of VMS was written in BLISS. None of those languages became popular.
Of course, they weren't available as free beer with source tapes.
> Apple replaced the original Macintosh OS with a system based on a Unix.
Only because they decided to buy NeXT instead of Be.
Had they bough Be, that would not been true at all.
jonjacky 18 hours ago [-]
> Of course, they weren't available as free beer with source tapes.
I think this was less important then, than people sometimes think.
I recall those days. In the 1980s and 90s I worked as a scientific programmer in a university department. Some of our software was commercialized and sold and supported as a product for a time in the 80s. Pardon the following long memoir, but I think some reporting on what actually happened then, as seen by even one participant, is pertinent.
We used a VAX with DEC's VMS operating system. Our application was developed in DEC Pascal (which didn't have the limitations of Standard Pascal because it used the DEC CLR, Common Language Runtime). Later on we began using Allegro Common Lisp for some things.
Through the 80s and early 90s, we never used Unix and C. And, we were not unusual, even in a university. Most of the VAXes at that university ran VMS
(or one of the DEC-10/20 OS in the early 80s), including the computer science department (which began running Unix on some but not all systems later in the 80s). So Unix was not as pervasive in the 80s as some people seem to think.
About "free beer": running Unix on a VAX in the 1980s was definitely not "free", it was a major investment in time, effort, and yes, money (in the form of salaries). First, the OS wasn't a separate line item. You bought a bundled system including both the VAX hardware and the VMS OS. Then the DEC guy came and turned it on and it just worked. I don't even know how buying a bare VAX and installing your own OS worked. How did you handle DEC field service?
They required their own utilities that ran on VMS. If you used Unix, you needed an expert in Unix to install it and maintain it.
And it was no different with the early commercial Unixes. You bought a Sun workstation and it came with their Unix bundled (Solaris or whatever).
In the 1990s we switched from VAX/VMS to HP workstations that bundled HP-UX, their Unix. In all of these Unix platforms, Unix was bundled and you did pay for it, it was just included in the price.
I think there is some confusion about the history. The free, frictionless, install-it-and-run-it-yourself OS was not Unix in the 80s, it was Linux in the 1990s. By then C and Unix-like operating systems were well established.
Also, there was genuine admiration for Unix technical features, notably its simplicity and uniformity, even at sites like ours that didn't use it. There were several projects to give VMS a Unix-like userspace. There was a (yes) free Software Tools project (that was its name), and a commercial product called Eunice. People who had already paid for VMS paid more for Enunice to make VMS
look like Unix.
Unix was a better platform for teaching CS than VMS or the other alternatives.
VMS did come with source code. It came on a huge stack of fiche cards, along with several pallet-loads of hardcopy documentation in binders.
There was nothing like the books The C Programming Language by K&R, or The Unix Programming Environment by Kernighan and Pike. Or the many Unix and C books that followed them. And then the college courses that used them.
Instead there were special courses in system programming and OS internals (separate courses) from DEC. The university would pay for them once in a while. A DEC expert would come for a week and programmers from all the VAX sites would get together all day every day in a classroom while they lectured. There was no textbook, but everyone got a huge binder of printed notes.
So systems programming on VMS, and I suppose other non-Unix platforms, remained an esoteric, inaccessible art, totally divorced from application programming, that used a programming language that was not used for anything else.
A few words comparing my experience programming in C in the 1990s to programming in DEC Pascal in the 80s: C wasn't much worse. The greater safety of Pascal did not make much difference in application programming. In Pascal, array-bounds errors etc. produced a crash with a traceback. In C similar errors produced a crash with a cryptic message like "segfault". But often the actual defect was far from the line that crashed, that appeared in the traceback, so the investigation and debugging was similar in both languages. But the more common (and often more difficult) errors that just computed the wrong answer were about the same in both languages.
smackeyacky 11 hours ago [-]
My recollection of working in a similar environment was very different. The Comp Sci department wanted Unix but not for its own sake. They wanted access to the burgeoning software being produced for it aimed at academics. Tex/LaTeX was the biggest driver because it was the best way at the time to make a readable research paper that was heavy in math.
Then the students needed access to lex/yacc etc for their courses and X Windows too.
That we produced other Unix programs was just an artifact of the original drive to have Unix. The Compaq 386 or Macintosh II were niche products for that job and VMS had been turfed by the late eighties.
heyitsdaad 1 days ago [-]
First to market is not necessarily the best, case in point: many video sites existed before Youtube, including ones based on Apple Quicktime. But in the end Flash won.
To me it looks like there is a better way to do things and the better one eventually wins.
> Operating systems have to deal with some very unusual objects and events: interrupts; memory maps; apparent locations in memory that really represent devices, hardware traps and faults; and I/O controllers. It is unlikely that even a low-level model can adequately support all of these notions or new ones that come along in the future. So a key idea in C is that the language model be flexible; with escape hatches to allow the programmer to do the right thing, even if the language designer didn't think of it first.
This. This is the difference between C and Pascal. This is why C won and Pascal lost - because Pascal prohibited everything but what Wirth thought should be allowed, and Wirth had far too limited a vision of what people might need to do. Ritchie, in contrast, knew he wasn't smart enough to play that game, so he didn't try. As a result, in practice C was considerably more usable than Pascal. The closer you were to the metal, the greater C's advantage. And in those days, you were often pretty close to the metal...
Later, on page 60:
> Much of the C model relies on the programmer always being right, so the task of the language is to make it easy what is necessary... The converse model, which is the basis of Pascal and Ada, is that the programmer is often wrong, so the language should make it hard to say anything incorrect... Finally, the large amount of freedom provided in the language means that you can make truly spectacular errors, far exceeding the relatively trivial difficulties you encounter misusing, say, BASIC.
Also true. And it is true that the "Pascal model" of the programmer has quite a bit of truth to it. But programmers collectively chose freedom over restrictions, even restrictions that were intended to be for their own good.
pjmlp 1 days ago [-]
The irony is that all wannabe C and C++ replacements are exactly the "Pascal model" brought back into the 21st century, go figure.
"A consequence of this principle is that every occurrence of every subscript of every subscripted variable was on every occasion checked at run time against both the upper and the lower declared bounds of the array. Many years later we asked our customers whether they wished us to provide an option to switch off these checks in the interests of efficiency on production runs. Unanimously, they urged us not to they already knew how frequently subscript errors occur on production runs where failure to detect them could be disastrous. I note with fear and horror that even in 1980 language designers and users have not learned this lesson. In any respectable branch of engineering, failure to observe such elementary precautions would have long been against the law."
-- C.A.R Hoare's "The 1980 ACM Turing Award Lecture"
phicoh 1 days ago [-]
The thing for me at least is that when I looked at Pascal, MODULA-2, Ada, if you had complex data structures which had to allocate and deallocate memory, then those language would not help at all. They would allow you to make pointer mistakes. Pascal and MODULA-2 were also very restrictive in various area (no generics). Ada is better in that respect, but Ada compilers were rare.
In my opinion it is only Rust that offers a language without runtime system requirement and fixes essentially all of the problems of C.
pjmlp 1 days ago [-]
First of all C did not had any generics, so same playing field.
C has a runtime, even if tiny. That is what calls into main(), handles floating point arithmetic when none is available, functions that run before and after main(), nowadays also does threading.
Heap memory handling in Pascal, Modula-2, Ada, is much safer than C, first of all no need to do math to calculate the right size, arenas are available on the standard library, dinamic allocation can also be managed by the compiler if desired (Ada), pointers are safe as they by default must be used with existing data, however if one really wants to do pointer arithmetic it is available.
The only issue that they have in regards to C, is the use-after-free, but that apparently isn't an issue for folks moving away from C into Zig, wich is basically Modula-2 with some C syntax flavour.
phicoh 1 days ago [-]
C uses pointer casts all over the place to fake generics. If you don't have that (in Pascal or MODULA-2) then life becomes very unpleasant.
There is a quite a bit of C code that makes creative use of the size of allocations. For example linked lists with a variable sized payload. Again one of the things that would prevent a C programmer from switching to Pascal.
I don't expect the Zig user base to become larger than the Rust user base any time soon. But we have to wait and see, Zig is quite young.
pjmlp 24 hours ago [-]
Same tricks are possible in Modula-2, Pascal, Ada, if fake generics count.
Creative use of the size of allocations are also possible in those languages, the BIG difference is that they aren't the default way everything gets done.
AnimalMuppet 21 hours ago [-]
In Pascal (not the original Pascal standard, but, say, Turbo Pascal), could you allocate a variable-sized array of something, and still have index protection when using it?
(I know quite well that C couldn't. Even a C++ vector may or may not, depending on which access method you use.)
Gibbon1 3 hours ago [-]
I've started describing what C does is breaking the third wall. Which in theater sis when the actors acknowledge they're in a play.
C totally accepts breaking the third wall. And Pascal doesn't.
Problem with C currently is the language is controlled by people that think programming languages should be like Pascal.
jonjacky 1 days ago [-]
> I'm not sure what you mean by "coincidence" or "accident" here.
I mean Unix had to be written in C, not in, say, Algol or PL/I or BLISS, high-level languages used to write other operating systems.
I also meant that the features of C were not put there by impulse or whim, they were the outcome of considered decisions guided by the specific needs of Unix.
pjmlp 1 days ago [-]
No it had not,
> Although we entertained occasional thoughts about implementing one of the major languages of the time like Fortran, PL/I, or Algol 68, such a project seemed hopelessly large for our resources: much simpler and smaller tools were called for. All these languages influenced our work, but it was more fun to do things on our own.
jonjacky 19 hours ago [-]
They say right there that Fortran, PL/I, and Algol 68 were too big and complicated for Unix. Yes, if you are building a system, it is more productive to use a language that is built for purpose and pleasant to work with ("fun") than one you have to struggle against all the time.
pjmlp 1 days ago [-]
They wanted to play and ignored other languages on purpose, that is all.
> Although we entertained occasional thoughts about implementing one of the major languages of the time like Fortran, PL/I, or Algol 68, such a project seemed hopelessly large for our resources: much simpler and smaller tools were called for. All these languages influenced our work, but it was more fun to do things on our own.
Pity that in regards to secure programing practices in C, community also ignores the decisions of the authors.
> Although the first edition of K&R described most of the rules that brought C's type structure to its present form, many programs written in the older, more relaxed style persisted, and so did compilers that tolerated it. To encourage people to pay more attention to the official language rules, to detect legal but suspicious constructions, and to help find interface mismatches undetectable with simple mechanisms for separate compilation, Steve Johnson adapted his pcc compiler to produce lint [Johnson 79b], which scanned a set of files and remarked on dubious constructions.
Also to be noted that on Plan 9 they attempted to replace C with Alef for userspace, and while the experiment failed, they went with Limbo on Inferno, and also contributed to Go.
And that C compiler on Plan 9 is its own thing,
> The compiler implements ANSI C with some restrictions and extensions [ANSI90]. Most of the restrictions are due to personal preference, while most of the extensions were to help in the implementation of Plan 9. There are other departures from the standard, particularly in the libraries, that are beyond the scope of this paper.
They were making it all up as they went along at a time when computers had 8, 12, 16, 18, 32, and 36 bit word lengths.
drnick1 1 days ago [-]
Yes and no. Clearly what you said is true, but the more profound reason is that C just minimally reflects how computers work. The rest is just convention.
haberman 1 days ago [-]
More concretely, I think the magic lies in these two properties:
1. Conservation of mass: the amount of C code you put in will be pretty close to the amount of machine code you get out. Aside from the preprocessor, which is very obviously expanding macros, there are almost no features of C that will take a small amount of code and expand it to a large amount of output. This makes some things annoyingly verbose to code in C (eg. string manipulation), but that annoyance is reflecting a true fact of machine code, which is that it cannot handle strings very easily.
2. Conservation of energy: the only work that will be performed is the code that you put into your program. There is no "supervisor" performing work on the side (garbage collection, stack checking, context switching), on your behalf. From a practical perspective, this means that the machine code produced by a C compiler is standalone, and can be called from any runtime without needing a special environment to be set up. This is what makes C such a good language for implementing garbage collection, stack checking, context switching, etc.
There are some exceptions to both of these principles. Auto-vectorizing compilers can produce large amounts of output from small amounts of input. Some C compilers do support stack checking (eg. `-fstack-check`). Some implementations of C will perform garbage collection (eg. Boehm, Fil-C). For dynamically linked executables, the PLT stubs will perform hash table lookups the first time you call a function. The point is that C makes it very possible to avoid all of these things, which has made it a great technology for programming close to the machine.
Some languages excel at one but not the other. Byte-code oriented languages generally do well at (1): for example, Java .class files are usually pretty lean, as the byte-code semantics are pretty close to the Java langauge. Go is also pretty good at (1). Languages like C++ or Rust are generally good at (2), but have much larger binaries on average than C thanks to generics, exceptions/panics, and other features. C is one of the few languages I've seen that does both (1) and (2) well.
ChrisSD 1 days ago [-]
It minimally reflects PDP-11 assembly, which is not how modern computers work.
uecker 1 days ago [-]
This is a meme which is repeated often, but not really true. If you disagree, please state specifically what property of PDP-11 you think it different from how modern computers work, and where this affects C but not other languages.
In a nutshell, the useful fiction of computer-as-Von-Neumann-meaning doesn’t adequately reflect the reality of modern hardware. Not only does the CPU itself not fit that model (with things like speculative execution, sophisticated power and load management…), but the system as a whole is increasingly an amalgamation of different processors and address spaces.
pjmlp 1 days ago [-]
It lacked SIMD instructions.
uecker 17 hours ago [-]
C compilers can emit SIMD instructions just fine and often have extensions to support writing it explicitly. Also few other languages have explicit support for them from the start and most have added them as some kind of extension later. So the idea that this is some fundamental computer architecture thing C got wrong seem pretty far-fetched. Support for multi-core processing would be a more plausible thing to look at, but even there it seems that C still does quite well.
tmtvl 1 days ago [-]
Don't forget about branch prediction (GCC may have __builtin_expect, but that's not standard C).
direwolf20 1 days ago [-]
The compiler usually can't do anything about branch prediction. Expect is more about keeping cold paths out of the cache.
pjmlp 23 hours ago [-]
Actually that was one of the problems with EPIC architectures, where compilers are expected to be the ones doing those kind of optimizations.
thayne 1 days ago [-]
The things complained about in the article are not a minimal reflection of how computers work.
Take the "wobbly types" for example. It would have been more "minimal" to have types tied directly to their sizes instead of having short, int, long, etc.
There isn't any reason that compilers on the same platform have to disagree on the layout of the same basic type, but they do.
The complaints about parsing header files could potentially be solved by an IDL that could compile to c header files and ffi definitions for other languages. It could even be a subset of c that is easier to parse. But nothing like that has ever caught on.
donkeybeer 1 days ago [-]
There were many different types of computers back then. Some even had 36 bit word sizes. I don't think there was any clear winner like amd64 back then that they could have prioritized. 16 and 32 bit machines existed in decent amounts and so on.
direwolf20 1 days ago [-]
For that, we have uint_fast32_t and so on.
pjmlp 1 days ago [-]
How does C reflect how AVX works?
stackghost 1 days ago [-]
> C just minimally reflects how computers work. The rest is just convention.
This hasn't been true for decades. x86 assembly is now itself an abstraction over what the CPU is actually doing.
Microcode, speculative execution, etc.
kristianp 1 days ago [-]
It seems to be a meme on HN that C doesn't reflect hardware, now you're extending that to assembly. It seems silly to me. It was always an approximation of what happens under the hood, but I think the concepts of pointers, variable sizes and memory layout of structs all represent the machine at some level.
ghosty141 1 days ago [-]
Its not a meme.
For example, C has pointer provenance, so pointers arent just addresses. Thats why type punning is such a mess. If a lang claims to be super close to the hardware this seems like a very weird thing.
1718627440 1 days ago [-]
C is super close to the hardware in that it works exactly like the abstract C machine, which is kind of a generalization of the common subset of a lot of machines, invented to make it portable, i.e. viable to be implemented straightforwardly on various architectures. For example pointer provenance makes it work on machines with segmented storage, these can occur anywhere, so there is no guarantee that addresses beyond a single allocation are expressible or meaningful.
What makes C feel free for programming is that instead of prescribing an implementation paradigm, it instead exposes a computing model and then lets the programmer write whatever is possible with that (and also what is not -- UB). And a lot of higher level abstractions are quickly implemented in C, e.g. inheritance and polymorphism, but then they still allow to be used in ways you like, so you can not just do pure class inheritance, but get creative with a vtable, or just use another vtable with the same object. These are things you can't do when the classes are a language construct.
ghosty141 1 days ago [-]
The C abstract machine is exactly the important part. There is a difference between saying C is close to "the hardware" and C is close to the C abstract machine. The latter like you described has a few concepts that allow for abstraction and thus portability but obviously they lead to situations where the "maps to the hardware" doesn't seem to hold true.
My gripe is only with people acting like the C abstract machine doesn't exist and C is just syntax sugar for a bit of assembly. It's a bit more involved than that.
direwolf20 1 days ago [-]
It remains close to computer hardware + optimiser. This provenance thing was introduced to aid optimisation.
Nevermark 1 days ago [-]
> the concepts of pointers, variable sizes and memory layout of structs all represent the machine at some level.
Exactly.
Everything in assembly is still one-to-one in terms of functional/stateful behavior to actual execution. Runtime hardware optimization (pinhole instruction decomposition and reordering, speculative branching, automated caching, etc.) give a performance boost but do not change the model. Doing so would mean it didn't work!
And C is still very close to the assembly, in terms of basic operations. Even if a compiler is able to map the same C operations to different instructions (i.e. regular, SIMD, etc.)
pjmlp 1 days ago [-]
Lets play a game of what ISO C can do, and no other systems programming language has similar feature available?
If language extensions to ISO C are allowed, then same goes for my selection on competing systems languages.
Nevermark 1 days ago [-]
Yes, most languages allow C type code, if that’s what you are trying to do.
Java with only primitive values, arrays, and classes only with fields and static methods.
But that wouldn’t be idiomatic Java, so typically non-explicit abstractions such as polymorphism have code generated for them that you don’t have explicit control over.
C is consistently low level because that’s all you get. Down to direct access to addressing and RAM, the stack frame, etc. as with assembly.
pjmlp 24 hours ago [-]
How do you do direct accessing to CPU registers in C as with Assembly?
Being idiomatic or not doesn't matter, what counts are what language features are available.
Nevermark 20 hours ago [-]
I am puzzled by the claim that C and assembly are not relatively close.
Note here “close” being used in the injective, not bijective, sense. (Scratch out “one-to-one” in my earlier comment.)
And “closer” lowers the bar here too. C isn’t simply decorated assembly. But closer to it.
And “close” being used informally. Arguments for closeness are several and strong (I think), but a bit of a hodgepodge.
In terms of non-bijectivity, for systems programming and performance choices C makes it easy to drop into assembly. But the former are uniquely application specific. And the latter doesn’t make the C version less like the assembly it maps onto - whether the compiler uses the more performant instructions for the context or not.
C’s convenient assembly inlining, and the handoff in both directions being smoothed by an assembly friendly model of the C code around it, are both a part of the “closeness”
But C is generally “close” to assembly, because its data types emphasize types handled natively, compound types reflect RAM layout, and pointers are explicit addresses to data and code. And those address values can be constructed and operated on just like any other data.
C is objectively closer to assembly than languages with strongly required abstractions. (E.g., Java classes, Lisp S-exp's/cons cells, etc.)
C is more “strictly closer” to assembly than languages with more optional abstractions, even if they also allow for relatively low level coding.
Functions could be viewed as a preferred abstraction, but they have a clear assembly level model accessible directly with pointer arithmetic. And they don’t get in the way of directly encoding custom argument passing schemes, and using goto’s and zero argument functions and tail calls as atomic assembly calls for function and jumps for continuations.
Types are a significant non-assembly abstraction, but are zero-cost in that they don't separate C from assembly, but C from C, as a code safety mechanism that is easily escaped.
It is often easy to add abstractions, via regular C, or macros, but you have to provide an explicit implementation for them in the source or complied library.
(However, if macros, with their mixed logical, symbol, text and file “data” model, are viewed as C source instead of as a C source construction language, then C becomes a very wacky abstraction language with behavior and rules that look nothing like simple assembly.)
kazinator 8 hours ago [-]
There is no "C's convenient inline assembly": that is a vendor extension, if available, and its convenience could vary considerably.
The manipulation of memory by C programs is close semantically to the manipulation of memory by assembly programs. Memory accessed through pointers is similarly "external" to both assembly language and C programs.
The evaluation of C program code is not close to assembly language. C programs cannot reflect on themselves portably; features like parameter passing, returning, and allocating local storage during procedure activation, are not in the programming model.
C loses access to detailed machine state. Errors that machine language can catch, like overflows, division by zero and whatnot, are "undefined behavior". An assembly language program can easily add two integers together and then two more integers which include the carry out from the previous addition. Not so in C.
Assembly language instruction set designs (with some exceptions) tend to bend over backwards to preserve the functioning of existing binary programs, by maintaining the illusion that instructions are executed in sequence as if there were no pipelining or speculative execution, or register renaming, etc.
Meanwhile, C compiler vendors bend over backwards to prove that code you wrote 17 years ago was wrong and make it fail. C is full of unspecified evaluation orders and various kinds of undefined behavior in just the basic evaluation model of its syntactic, built-in constructs; and then some more in the use of libraries.
In assembly language, you would never have doubt about the order of evaluation of arguments for a procedure.
Even when it comes to memory, where C and asasembly language agree in many points, there are some subtle ways C can screw you. In assembly language, you would never wonder whether copying a structure from one memory location to another included the alignment padding bits. In C you also don't have to wonder, if you use memcpy. Oh, but if you use memset to clear some memory which you don't touch afterward and which goes out of scope, the compiler can optimize that away, oops!
jenadine 14 hours ago [-]
> I am puzzled by the claim that C and assembly are not relatively close.
Did anyone say that?
I think the point is not that it is not "close", but that C is not equivalent to ASM: C has its own abstractions, and there are things you can do on assembly that you can't express in C.
The other low level languages such as C++, Rust, Zig, ... are equally close since you can express the same things. In some respect they are even closer since they got more features builtins that modern assembly can now do that was not part of the design in C. (SIMD, threading, ...)
Modern languages also have extra abstractions that makes programming easier without compromising on the cost. There are more abstractions than in C, but they also are optional. (Just like you could use goto instead of while or for loop, but you're happy this abstractions exist. We could also use functions pointer in C++ instead of virtual functions, but why would we if the language provide tools that make programming easier, for the same result)
Nevermark 6 hours ago [-]
As I noted, closeness has several meanings.
> The other low level languages such as C++, Rust, Zig, ... are equally close since you can express the same things.
C is not just low level friendly, but low level out of the box. That is the level that all C must be written in, even when creating higher abstractions.
Some higher level languages are also low level friendly, not low level strict. Which is a kind dual.
I would argue that what makes C lower level, is that it comes in at, or under, the low levels of other languages, and its high bar comes in much lower than the abstractions built into other languages.
Forth is a good candidate for being even lower level.
But if someone else doesn't see things that way, that is fine. It is just one lens for comparing languages.
jenadine 5 hours ago [-]
> C is not just low level friendly, but low level out of the box. That is the level that all C must be written in
No, it is not:
- People use for/while loop, for example, instead of the "low level" 'goto'
- C compiler compute pointer aliasing, assume operations don't overflow, etc., in order to optimise your code: What you write doesn't translate directly to assembly.
- Some low level operations cannot even be represented in pure C (without using __asm__ extension escape hatch)
kreco 1 days ago [-]
I'm not sure I agree with "impossible to change".
It's 2026, to this date I cannot use standard library/api, to open a file with utf-8 filename without a null terminating string.
When you want to talk to the OS you constantly face the need to had unnecessary overhead (allocation due to string convertion, strlen).
The OS itself does not prevent anything from having those standard "no overhead" API.
However, it's pretty clear that nobody cares to define some new sane interface and nobody care to deprecate old ones.
That would include both API and ABI.
phicoh 21 hours ago [-]
Maybe because the performance gain is just not there. Adding support for string with explicit length everywhere is a huge amount of work. And then the question is whether such a string is like a Rust slice or something else.
And then the gain is close to zero because most filenames are short enough that there is almost no gain.
kreco 18 hours ago [-]
The mental overhead is pretty significant.
You need to do weird string operations, you have certainly a class somewhere that needs to append a zero to then end of a buffer, and exclusively use the class for thw filename.
You can't just toss a contiguous number of bytes you to convert it first.
Every single piece of software that need to interact with the file system needs to deal with this.
I'm not asking about a new string type. I'm asking to be able to be free from null terminating string.
You only need to provide a length.
pizlonator 1 days ago [-]
Yes
munificent 1 days ago [-]
Care to share the answer with the rest of the class?
uecker 1 days ago [-]
I am not sure what Filip's view on this is. But like to point out the article from Stephen Kell linked below which explains why C is an incredibly useful tool for systems programming and what distinguishes it from all other languages.
The author is upfront about their goals and motivations and explicitly acknowledges that other concerns exist. Calling it whiny is ungracious -- the author is letting some very human frustration peek through in their narrative.
Not everything has to be written with all the warmth and humanity of a UN subcommittee interim report on widget standardisation.
The writing is terrible and full of fluff. Maybe the cat logo should have been a warning.
1vuio0pswjnm7 1 hours ago [-]
Rust is a good way to get bad C programmers to stop using C
But Rust evangelists cannot stop people from using any particular language
Assembly is the only language that matters. It's the
only language the computer "understands"
Everything other language is just an abstraction over it
When it comes to abstractions not everyone has the same preferences
smallstepforman 1 days ago [-]
C’s biggest sins (also inherited by C++):
- unspecified default type sizes. Should have had i8, u16, i32, u64, f32, f64 from the beginning.
- aliasing pointers being restricted by default (ie an alias keyword should have been added). Performance matters. All these benchmarks which show something beating C or C++ are mostly due to dealing with aliasing pointers. C++26 still doesnt have standardised restrict keyword.
There are more but I understand the logic/usability/history behind them. The above points should have been addressed in the 80’s.
abcd_f 1 days ago [-]
Arrays decaying to pointers is probably the biggest non-platform specific design oversight.
As you said, it's easy to see where it came from, but it should've been fixed long ago.
1718627440 1 days ago [-]
> but it should've been fixed long ago.
Is 27 years for you not long ago enough? That's more than a generation away and closer to the invention of the language than today.
pjmlp 22 hours ago [-]
Worse than that, lets remember that WG14 rejected Dennis Ritchie proposal for fat pointers, and the C authors decided it was more fun to keep their own way with other programming languages than try to improve C from WG14.
Not the error "handling"? The array implementation? The weak type system? The barebones-macro-system? The nearly unuseable standard-library? The standard itself, a 750-page-tome you have to memorize, lest C is allowed to erase your hard drive?
C is sin incarnated.
1718627440 1 days ago [-]
In my opinion error handling in C is great. It forces you to actually think about it, deal with them as close as possible and makes you write it in a forward compatible way. Much better than exceptions were you never no what one might throw in another version.
> The barebones-macro-system?
The CPP is a different language and designed that way so you can use another language that suits you better, most don't do that, because the default is fine.
donkeybeer 1 days ago [-]
Which systems in the 70s or even 90s would have had good hardware support for all these lengths?
I think overall perhaps its better that the thing named int works on both the arduino and my amd64 otherwise we might needlessly need to rename all types for each platform to fit the most natural type lengths there. Imagine the 32 bit to 64 bit transition under those conditions.
danhau 1 days ago [-]
All of them, through software implementation, as assembly programmers have done since forever.
You simply choose the integer type that your problem or task requires. If the hardware genuinely can‘t cope with it (performance), you reevaluate your requirements, define new constraints and choose new types. This is basic requirements engineering, which C only made more convoluted.
donkeybeer 1 days ago [-]
I think overall it's better to provide "natural" default types but also have had something like stdint.h. But then again mandatong even a stdint.h that early would have made writing implementations quite difficult. I think it was and is an alright tradeoff.
direwolf20 1 days ago [-]
But it doesn't work. You use an int to hold milliseconds, it works on your machine, and when compiled for Arduino (16–bit int) it wraps around every minute.
donkeybeer 24 hours ago [-]
We have stdint in modern systems so when the length of a type is a hard requirement then we can use whatever type or software emulate a larger word. Otherwise I think keeping the default types for things like eg number of elements seems alright.
direwolf20 4 hours ago [-]
So you've backtracked from most variables, to number of elements.
For number of elements there's size_t. So again, when are "natural types" the right answer?
donkeybeer 2 hours ago [-]
When the C was invented? I feel it'd have been much more burdensome and inhibited the authors of various compilers if they had to write software or otherwise emulation of all these lenghths on the archs of the time. Still today I'd say, you cited size_t, I think that's the most important type, otherwise we shouldn't make most uses of integers as named fixed lengths unless we are specifically performing some mathematical operation like finite field arithmetic where the bit length matters or as you noted you know your problem size might be large enough that some archs machine words are too small. Otherwise I don't see why we shouldn't just default to natural lengths. Otherwise we need to write different int types for each arch and that doesn't sound nice unless we explicitly see a reason to.
donkeybeer 2 hours ago [-]
Tbh I am finding it a bit hard to think of a use of integers that either isn't a length or a mathematical operation, so I gotta say it does narrow things down.
direwolf20 4 hours ago [-]
The length of a type is always a hard requirement.
1718627440 1 days ago [-]
> - unspecified default type sizes. Should have had i8, u16, i32, u64, f32, f64 from the beginning.
I strongly disagree. The programmer should rather prescribe intent and shouldn't constantly think about what size this should exactly have. Does this variable represent the size of an object, an address, a difference or just a random positive integer? Then use size_t, uintptr_t, ptrdiff_t and unsigned int respectively. Why should I care what exact sizes these are? I hate that modern (system) languages completely throw away that concept. "I want a unsigned integer" "oh, you mean u32" "No! I really mean an unsigned integer."
Also when I do want e.g. 32 bit available, there is no reason, I need to use a suboptimal 32 bit wrapping behaviour when I don't need it. The correct type to use for computation for that would be uint32_fast_t and the implementation chooses what makes sense to use for this.
danhau 1 days ago [-]
How will you know if your integer type is adequate for the problem at hand, if you don‘t know its size?
Choosing the right type is a function of signedness, upper/lower bound (number of things), and sometimes alignment. These are fundamental properties of the problem domain. Guesstimating is simply not doing the required work.
kevin_thibedeau 20 hours ago [-]
C specifies minimum sizes. That's all you need 99% of the time. I'm always annoyed by the people who assume int is 32-bits. You can't assume that in portable code. Use long or ensure the code works with 16-bit ints. That is how the type system was meant to be used. int was supposed to reflect the natural word size of the machine so you could work with the optimum integral type across mismatched platforms. 64-bit platforms have mostly abandoned that idea and 8-bit never got to participate but the principle is embedded in the language.
donkeybeer 1 days ago [-]
The idea is mostly that we shouldn't worry. The user of the lib on an arduino will feed it arduino sized problems and the amd64 user will likewise larger problems. Again I think just think of the transition from 32 to 64 bit. Most ranges are "input"/"user " dependent and it would have been needlessly messy to have to even with automatic conversion help rewrite say every i32 to i64 or which ones to convert.
direwolf20 24 hours ago [-]
That's size_t. What about counting milliseconds?
donkeybeer 24 hours ago [-]
As I said, today when it really matters we can use stdint. But I feel it would have been too burdensome to mandate on the standard in the early days of C.
Borg3 24 hours ago [-]
Like fucking what? If you do any TS.. you just use unsigned whatever fastest type you have on targeting platform, and you do NOT care. 16bit? wrapping at 1 min? Thats eternity even on 2MHz 6502... You just count and substract to get difference. Usually you are <1000ms range.. so wrapping is not a problem..
If you target about 32bit and 16bit, you either think about and using long (on 16 bit is more costly) or you just count seconds.. Or ticks.. or whatever you need.
direwolf20 23 hours ago [-]
cool, it's an app that lets me set a 2 minute timer and counts in milliseconds, oh what's that, it doesn't work right on Arduino
Borg3 18 hours ago [-]
Then use long.. Does Arduino CPU cannot do 16bit add/sub with carry? wtf?
direwolf20 15 hours ago [-]
I'm not writing the app. The app was written according to your preferred design and I'm compiling it for Arduino. You say to just use int because it always has enough bits, then you say to sometimes use long because int might not have enough bits.
Borg3 12 hours ago [-]
I dont know the indented use. If you need delay or difference, 16bit is more than enough. If you writting generic clock w/ ms accuracy, it will be not enough.
You either split it or use larger storage. Its not rocket science...
1718627440 13 hours ago [-]
There is a platform where int has less than the prescribed 16 bits? How much does it have then, just 8bit or something weird?
direwolf20 4 hours ago [-]
Arduino has 16-bit int, so the timer app will work up to 32.767 or 65.535 seconds, that is less than two minutes.
1718627440 2 hours ago [-]
When you choose unsigned int for that type, you compare against UINT_MAX and return an EOVERFLOW, ENOMEM, EOUT_OF_RANGE or whatever you like when someone sets a timer greater than that. Or you choose another type, i.e. unsigned long which is guaranteed to take values to at least 4294967295. I happen to program for the Arduino platform recently and milliseconds in the Arduino API are typed unsigned long.
If your decision is that you really want to store all 32-bit values then you use uint_least32_t or uint_fast32_t depending if you are also resource constrained.
direwolf20 24 hours ago [-]
Your problem demands a certain number of bits. Counting milliseconds in a month needs 32 bits. It's not just a random number.
1718627440 15 hours ago [-]
Then you use an integer type that has at 32-bits? uint32_fast_t for computation, uint32_least_t for storage, if you are resource constrained.
direwolf20 4 hours ago [-]
This is never not the case. You always need to specify the number of bits.
mjg59 1 days ago [-]
I really don't understand why people keep misunderstanding this post so badly. It's not a complaint about C as a programming language. It's a complaint that, due to so much infrastructure being implemented in C, anyone who wants to interact with that infrastructure is forced to deal with some of the constraints of C. C has moved beyond merely being a programming language and become the most common interface for in-process interoperability between languages[1], and that means everyone working at that level needs to care about C even if they have no intention of writing C.
It's understandable how we got here, but it's an entirely legitimate question - could things be better if we had an explicitly designed interoperability interface? Given my experiences with cgo, I'd be pretty solidly on the "Fuck yes" side of things.
(Of course, any such interface would probably end up being designed by committee and end up embodying chunks of ALGOL ABI or something instead, so this may not be the worst possible world but that doesn't mean we have to like it)
[1] I absolutely buy the argument that HTTP probably wins out for out of process
drnick1 1 days ago [-]
I don't see that as a problem. C has been the bedrock of computing since the 1970s because it is the most minimal way of speaking to the hardware in a mostly portable way. Anything can be done in C, from writing hardware drivers, to GUI applications and scientific computing. In fact I deplore the day people stopped using C for desktop applications and moved to bloated, sluggish Web frameworks to program desktop apps. Today's desktop apps are slower than Windows 95 era GUI programs because of that.
mjg59 1 days ago [-]
Ok you're still missing the point. This isn't about C being good or bad or suitable or unsuitable. It's about whether it's good that C has, through no deliberate set of choices, ended up embodying the interface that lets us build rust that can be called by go.
drnick1 1 days ago [-]
Yes, because C is, by virtue of its history and central role in the development of all mainstream operating systems, the lowest common denominator.
Also, if I remember correctly, the first Rust and Go compilers were written in C.
mjg59 1 days ago [-]
Yes! It's easy to see why we got here, but that doesn't mean it's the optimal outcome!
pjmlp 22 hours ago [-]
Rust used OCaml, and Go only used C, because it was partially created by the C authors, and they repurposed the Plan 9 C compiler for Go.
Usually it helps to know why some decision was taken, it isn't always because of the technology alone.
zorobo 1 days ago [-]
OCaml was used for rust.
Ygg2 1 days ago [-]
> Yes, because C is, by virtue of its history
Sure history is great and all, but in C it's hard to say reliably define this int is 64-bit wide, because of the wobbly type system. Plus, the whole historical baggage of not having 128-bit wide ints. Or sane strings (not null terminated).
thayne 1 days ago [-]
> in C it's hard to say reliably define this int is 64-bit wide
That isn't really a problem any more (since c99). You can define it as uint64_t.
But we have a ton of existing APIs that are defined using the wobbly types, so we're kind of stuck with it. And even new APIs use the wobbly types because the author didn't use that for whatever reason.
But that is far from the only issue.
128 bit ints is definitely a problem though, you don't even get agreement between different compilers on the same os on the same hardware.
pjmlp 1 days ago [-]
Of some computing platforms.
Ygg2 1 days ago [-]
> I don't see that as a problem.
It kinda is. Because it was made in the 1970s, and it shows (cough null-terminated strings uncough).
Or you know having a 64-bit wide integer. Reliably.
You did read the article, right?
tragiclos 1 days ago [-]
> could things be better if we had an explicitly designed interoperability interface?
Yes, we could define a language-agnostic binary interoperability standard with it's own interface definition language, or IDL. Maybe call it something neutral like the component object model, or just COM[1]. :)
The general idea is sound. The implementation less so.
layer8 14 hours ago [-]
Of course things could be better. That doesn’t mean that we can just ignore the constraints imposed by the existing software landscape.
It’s not just C. There are a lot of things that could be substantially better in an OS than Linux, for example, or in client-server software and UI frameworks than the web stack. It nevertheless is quite unrealistic to ditch Linux or the web stack for something else. You have to work with what’s there.
SanjayMehta 1 days ago [-]
VHDL vs Verilog is a good parallel from the chip world. VHDL was designed from ground up.
Verilog is loosely based on C. Most designs are done in Verilog.
pjmlp 1 days ago [-]
VHDL tends to reign in European hardware companies.
SanjayMehta 8 hours ago [-]
And Japan, I'm told.
direwolf20 24 hours ago [-]
I wonder why there aren't many successful European hardware products.
pjmlp 24 hours ago [-]
I would assert any company that doesn't go bankrupt is successful, doesn't need to be late stage capitalism.
Other than that, Nokia until Microsoft placed an agent on it, Phillips that contributed to CDs, ASML...
direwolf20 23 hours ago [-]
Three is not many
ahartmetz 20 hours ago [-]
ST, Infineon, ARM(!!), NXP, Raspberry Pi Holdings, Nordic Semiconductor (original developers of AVR, now known for Bluetooth chips and auch)...
pjmlp 22 hours ago [-]
" any company that doesn't go bankrupt is successful"
therealcamino 24 hours ago [-]
Well, VHDL was heavily based on Ada.
Animats 1 days ago [-]
The trouble with C as an API format is that there's no size info. That's asking for buffer overflows.
There's an argument for full type info at an API, but that gets complicated across languages. Things that do that degenerate into CORBA. Size info, though, is meaningful at the machine level, and ought to be there.
Apple originally had Pascal APIs for the Mac, which did carry along size info. But they caved and went with C APIs.
agentultra 1 days ago [-]
I mean… an ABI is more like an agreement. It isn’t a specification. It’d be nice if everything was neatly specified and sound. But as the author notes near the end… there’s a lot of platforms and they all have their own quirks.
There has to be an ABI that has to be agreed upon by everyone. Otherwise there wouldn’t be any interoperability. And if we didn’t have the SystemV ABI — what would we use instead? Prepare for a long debate as every language author, operating system designer, and platform under the sun argues for their respective demands and proposals. And as sure as the sun rises in the East someone, somewhere, would write an article such as this one decrying that blessed ABI.
SystemV shouldn’t be the be all and end all, IMO. But progress should be incremental. Because a lingua franca loses its primary feature and utility when we all return to our own fiefdoms and stop talking to one another in the common tongue.
It’s a pain in the metaphorical butt. But it’s better, IMO, than the alternatives. It’s kind of neat that SystemV works so well let alone at all.
mayhemducks 21 hours ago [-]
I prefer pragmatism over ontology. The word "is" complicates discussions in unproductive ways.
Good read though. Thinking about C as not just a language but also a protocol is a different perspective that is useful for the mental model.
hacker_homie 1 days ago [-]
It's not that it was made this way to annoy you, it was all we had.
The whole world shouldn't "need to be fixed" because you won't spend the time to learn something.
Rust doesn't even have a stable Internal ABI that's why you have to re-compile everything all the time.
cornhole 2 hours ago [-]
doesn’t mean it can’t be improved, but also doesn’t guarantee that the new way isn’t some sort of brand new hell
groundzeros2015 1 days ago [-]
What languages does this author like which has a written spec?
ChrisSD 1 days ago [-]
This article isn't about languages. It's about the protocol for two or more languages to talk to each other. There is no specification for this.
The System V ABI is as close as we get to an actual specification but not everyone uses it and in any case it only covers a small part of the protocol.
groundzeros2015 23 hours ago [-]
> He’s trying to materially improve the conditions of using C itself as a programming language.
> I’m trying to materially improve the conditions of using literally any language other than C.
president_zippy 6 hours ago [-]
OP, I would strongly recommend you try to play devil's advocate against your own case before making it, so you have a chance to refine your thoughts and rebut at least the top 2-3 counterarguments. More than that, you would do well to understand the concept of Chesterton's Fence as it applies to engineering:
"In the matter of reforming things, as distinct from deforming them, there is one plain and simple principle; a principle which will probably be called a paradox. There exists in such a case a certain institution or law; let us say, for the sake of simplicity, a fence or gate erected across a road. The more modern type of reformer goes gaily up to it and says, "I don't see the use of this; let us clear it away." To which the more intelligent type of reformer will do well to answer: "If you don't see the use of it, I certainly won't let you clear it away. Go away and think. Then, when you can come back and tell me that you do see the use of it, I may allow you to destroy it.""
-G.K. Chesterton
#1: What you consider a "language" is one with its own official runtime. By your logic, Javascript is not a language either because the interpreter is usually written in C++ (e.g. V8, spidermonkey).
#2: Dogfooding a compiler and a runtime is common practice because most people believe that it will help the makers of a language identify bugs in their implementation or identify undefined behavior in the very definition of their language. However, every language must bootstrap from another language: most languages bootstrapped from a compiler written in C. If you trace the lineage from compiler to compiler to ... compiler, you'll most likely find that the second C compiler ever written was written in C and the first compiler was written in some architecture's assembly language. The same still applies for the new, new language of C99 which is less than half the age of C. C is so mature that rooting out new undefined behaviors is no longer a significant concern.
#3: libc is not just a "language runtime", it's the interface into the OS kernel for most operating systems. The runtime for every "real language" as you describe it ultimately uses libc. It doesn't make sense to separate the functions in the C99 standard from the OS's own syscalls and special-purpose user land functions call C standard library functions, and C's standard library relies on the OS's special functions. If they were separate libraries, there would be a circular dependency.
Think of C not as "not a language" but as a language that serves the unusual purpose of being the foundation for nearly everything else.
You have a point on integer types being a mess, but we have stdint.h for that.
chuckadams 21 hours ago [-]
I remember reading some crank on slashdot decades ago seriously claiming that Windows was not an operating system. This type of argument hasn’t gotten any more intelligent since.
Panzerschrek 1 days ago [-]
> Anyone who spends much time trying to parse C(++) headers very quickly says “ah, actually, fuck that” and asks a C(++) compiler to do it.
That's exactly my case. For my programming language I have wrote a tool for C headers conversion using libclang. And even with help of this library it wasn't that easy, I have found a lot of caveats by trying converting headers like <windows.h>.
bfrog 22 hours ago [-]
C isn't really the protocol though, its just a way every language has of exporting simple symbols. Otherwise how else does this work, you'd never every language to understand every other languages symbol name encoding scheme, some of which are complete jibberish (I'm looking hard your way C++).
The real protocol in action here is symbolic linking and hardware call ABIs.
You could always directly call Rust functions, but you'd have to know where to symbolically look for them and how to craft its parameters for example.
If this is well defined then its possible. If its poorly defined or implementation specific (c++) then yeah its a shit show that is not solvable.
anthk 24 hours ago [-]
C under Plan9 it's pretty easy to understand. Under current BSD's and worse... GNUs, it's a nightmare.
With C++ it's the same. Within the Haiku code it's half understandable, the whole spec it's to get driven mad in days.
Woodi 1 days ago [-]
Do someone pays for anti-C propaganda ?? All that logic breaking accusations...
Eg. here, from memory:
> ...you want to read 32 bits from file but OH NOOES long is 64 bit ! The language ! The imposibility !
But when you read something ot unserialize some format you just need to know based on format schema or domain knowledge. Simple and straightforward like that ! You do not do some "reflections" on what language standard provide and then expect someone send you just that !!
So that anti-C "movement" is mostly based on brainless exampless.
Not saying C is perfect.
But it is very good and I bet IBM and other big corps will keep selling things written and actively developed in C/C++ + adding hefty consulting fees.
In the meantime proles has been adviced to move to cpu-cycle-eating inferior languages and layers over layers of cycle burning infra in cloud-level zero-privacy and guaranteed data leaks.
Oh, btw. that femous Java "bean" is just object with usually language delivered "basic type"... How that poor programmer from article should know what to read from disc when he just have types Java provides ?? How ? Or maybe he should use some domain knowledge or schema for problem he is trying to solve ??
And in "scripting language" with automatic int's - how to even know how many bits runtime/vm actually use ? Maybe some reflection to check type ? But again how that even helps if there is no knowledge in brain how many bits should be read ?? But calling some cycle burning reflection or virtual and as much as posible indirect things is what fat tigers love the moust :)
themafia 1 days ago [-]
> C was elevated to a role of prestige and power
We didn't do it to annoy you or to foist bad APIs on you. We did it because it was the best language for writing machine code at the time. By miles. Not understanding why this is true will lead you to make all the same mistakes the languages "bested" by C made.
orionblastar 2 days ago [-]
I always thought that C was a stepping stone to learn other languages. Like Pascal, it was educational to learn. My Comp Sci courses in 1986-1990 used Turbo Pascal and Turbo C.
twangist 1 days ago [-]
C was never a gateway to any flavor of Pascal, a "police state language".
Nevermark 1 days ago [-]
It's not a coincidence that Rust was invented in 1984 by some animals on a farm! Stay in thy lane, programmer!
TZubiri 1 days ago [-]
I think so to, for most devs C is like Latin, or Roman Law, not something we develop and use, but rather something we learn for context and to understand future developments.
There's some people that still develop on C for sure, but it's limited to FOSS and embedded at this point, Low Level proprietary systems having migrated to C++ or Rust mostly.
I agree with the main thesis that C isn't a language like the others, something that we practice, that it's mostly an ancient but highly influential language, and it's an API/ABI.
What I disagree with is that 'critiquing' C is productive in the same way that critiquing Roman Law or Latin or Plato is productive, the horse is dead, one might think they are being clever or novel for finding flaws in the dead horse, but it's more often a defense mechanism to justify having a hard time learning the decades of backwards compatibility, edge cases and warts that have been developed.
It's easier to think of the previous generation as being dumb and having made mistakes that could have been fixed, and that it all could be simpler, rather than recognize that engineering is super complex and that we might as well dedicate our full life to learning this craft and still not make a dent.
I applaud the new generation for taking on this challenge and giving their best shot at the revolution, but I'm personally thinking of bridging the next-next generation and the previous generation of devs, the historical complexity of the field will increase linearly with time and I think if we pace ourselves we can keep the complexity down, and the more times we hop unto a revolution that disregards the previous generation as dumb, the bigger the complexity is going to be.
uecker 1 days ago [-]
There is also still a lot of low-level proprietary code developed in C. I would guess far more than what is developed in Rust.
I fully agree about your last point. The proposed solutions to some of the deficiencies of C are sometimes worse than the disease while its benefits are often exaggerated, at the same time adding unnecessary layers of complexity that will haunt us for decades. In contrast, my hope would be to to carefully revise the things we have, but this takes time and patience.
TZubiri 19 hours ago [-]
>There is also still a lot of low-level proprietary code developed in C. I would guess far more than what is developed in Rust.
Do you mean that there's still code being developed, or that it still exists? Because I meant the former, the latter is true of a lot of dead languages like fortran and cobol, and it would cement the idea of being a dead language.
pklausler 18 hours ago [-]
Fortran has its problems with its standard and portability, but it is hardly a dead language yet.
uecker 18 hours ago [-]
I meant there is a lot more new low-level C code being developed than Rust code.
black_13 1 days ago [-]
[dead]
bigbuppo 1 days ago [-]
TL;DR author loves rust and writing a subset of a c compiler is hard
Ygg2 1 days ago [-]
Except the author has moved on from Rust, and is still fighting ABI hellscape in different language - Swift.
pjmlp 1 days ago [-]
Which has a standard ABI.
bigbuppo 10 hours ago [-]
With all the ABI standards and non-standard ABIs, what we really need is a unifying standard ABI.
TZubiri 1 days ago [-]
[flagged]
CGamesPlay 1 days ago [-]
This is just an ad hominem attack. Doesn't seem like the author is "in over their head"; they seem to have a pretty solid grasp of actual identifiable gaps between implementations and various specs, and the article was written with the same kind of "chastising" tone as you would see from any grey-bearded hacker who's unsatisfied with the way things are.
player1234 1 days ago [-]
[dead]
yoshuaw 22 hours ago [-]
This is the same author who found and reported ABI incompatibilities between clang and gcc: https://faultlore.com/abi-cafe/book/trophies.html — Given these incompatibilities have since been addressed, I think it's fair to say there were indeed issues with C.
foltik 22 hours ago [-]
Regardless of your opinions about the author, that’s a pretty shallow and condescending take.
SanjayMehta 1 days ago [-]
It is exactly as you describe.
majorchord 1 days ago [-]
[flagged]
TZubiri 1 days ago [-]
I think we can be accomodating of a wide array of diagnosable mental conditions in software. I'm thinking of Terry King's TempleOS, Ken Reitz's Requests.
Being upfront about it by authors dispels a lot of the potential tension and substantially changes the way we interact. I understand there may be a conflict and not everyone will want to advertise their diagnosis, but in my experience once it becomes clear that's what's going on, it helps all the parties involved.
ranger_danger 1 days ago [-]
I try not to put much stock in black-and-white opinions because I think the answer is rarely that simple.
Rendered at 12:37:18 GMT+0000 (Coordinated Universal Time) with Vercel.
Carpenters, plumbers, masons & electricians work on houses 3-300 yrs old, navigate the range of legacy styles & tech they encounter, and predictably get good outcomes.
Only C has, yet, given use that level of serviceability. C99, baby, why pay more?
When there’s an alternative that can compete with that sort of real-world durability, C will get displaced.
Even the most relevant C compilers are no longer written in C.
Worth to point out that most of the C compilers are also C++ compilers.
So the point is kind of distorted.
LLVM is written in C++, also supports multiple language frontends, one of them happens to be C.
Visual C++, also written in C++, happens to still compile some form of C, and after adding some C11 and C17 support (a few items are still missing in 2026), Microsoft is in no rush to add anything else.
The language that matters is the implementation, otherwise we can also talk about the relevance of PL/I compilers from IBM and Unisys, written in C++.
On the contrary, Lisp outshines C to a large degree here. Success has nothing to do with technical merit (if such a thing even exists), it's not a rational game.
In short: Lisp is semantic and geared towards a living system. The basic REPL is sh + cc + ld + db (and a few others) all in one. It's almost a little mind bending how nice these systems are put together, how cleanly they work. C is like pulling teeth in comparison.
I'm not even a fan of Lisp or sexpr languages. But it's the obvious heavyweight champion of longetivity and ultra-pragmatic service record... Yes, even in the systems domain.
A better example might be Guix, depending how “operating system” is defined.
- People just stopped caring about operating systems research and systems programming after ~2005. Actual engineering implementations of the concepts largely stopped after the second half of 90s. Most developers moved on to making websites or applications in higher level programming languages.
- C hit a perfect balance of being a small enough language to grok, being indepedent of the system manufacturers, reflecting the computer architecture of 80s, actually small in syntax and code length and quite easy to implement compilers for. This caused lots of legacy software being built into the infrastructure that gave birth to the current contemporary popular OSes and more importantly the infrastructure of the Internet. Add in .com bubbles and other crises, we basically have/had zero economic incentive to replace those systems.
- Culture changed. We cared more about stability, repairability and reusability. Computers were expensive. So are programmers and software. Now computers are cheap. Our culture is more consumerist than ever. The mentality of "move fast and break things" permeated so well with economic policy and the zeitgeist. With AI it will get worse. So trying to make a real alternative to C (as a generic low level OS protocol) has reduced cultural value / optics. It doesn't fill the CVs as well.
It doesn't mean that people haven't tried or even succeeded. Android was successful in multiple fronts in replacing C. Its "intents" and low level interface description language for hardware interfaces are great replacement for C ABI. Windows' COM is also a good replacement that gets rid of language dependence. There are still newer OSes try like Redox or Fuchsia.
Unfortunately I think Don Box’s was the only person in the world who really understood it all.
Using it from MFC, kind of alright.
Using it from .NET, depends if Framework, .NET Native, or modern, with various levels of usability.
Using it from ATL, WRL, C++/WinRT, is a mess unfortunely.
and so it was that after that date, all development of
came to a grinding halt, and no further work was done.It is better and higher performing hardware but until Rust and Zig arrived, the most popular ways of designing system-level software stayed the same. RTOSes work the same as how they work in late 90s / early 00s. C ABI is still the majority of communication interface. Interacting with OS using system calls stayed the same. After virtual memory and paging no big change in OS design happened. Main programming design patterns in C and C++ also stayed the same.
One area that stayed interesting is GPU programming. Nowadays CPUs basically provide us a PDP-11 simulator. Most of the time you don't need to recompile programs to harness most of the gains from a CPU. GPUs expose more of their internal hardware detaila than CPUs and unlike CPUs you need to recompile programs (which is what a GPU userspace driver does) to use newer models.
"Designing (New) C++ Hardware" - https://www.youtube.com/watch?v=86seb-iZCnI
And now with tiled architecture, Python JIT as well.
I am not sure I buy this from a system perspective, especially when taking this[1] into consideration.
______
1. Alexis King's reply to "Why do common Rust packages depend on C code?". Link: https://langdev.stackexchange.com/a/3237
It cares about calling conventions and what you can store in registers vs what you cannot. There are multiple possible ways of doing an RPC call and C ABI only provides one way of doing it.
Now you can move the goal posts and assert that any data serialized into a memory buffer is an array of ints.
There was no Rust at that point, and I used the most basic tool that could do it.
Could I have done this in Java with gymnastics of JNI, linking C into the JRE?
Definite maybe.
Unless one is stuck with Android Java, Google's J++.
IMO I do see this changing in the future as higher power computers become expensive once again, and I'm not just referring to the recent chip shortage.
free_sized
#embed
static_assert
Types for enum
Alignof, alignas, aligned_alloc
_Atomic
I could write a whole essay about why, but now isn’t the time. I’m just going to enjoy the fact that TFA and the author don’t get it.
Is there an answer here more interesting than "it's what Unix and Windows were written in, so that's how programs talked to the OS, and once you have an interface, it's impossible to change"?
C is a pretty OK language for writing an OS in the 70s. UNIX got popular for reasons I think largely orthogonal to being written in C. UNIX was one of the first operating systems that was widely licensed to universities. Students were obliged to learn C to work with it.
If the Macintosh OS had come out first and taken over the world, we'd probably all be programming in Object Pascal.
When everyone wanted to program for the web, we all learned JavaScript regardless of its merits or lack thereof.
I don't think there's much very interesting about C beyond the fact that it rode a platform's coattails to popularity. If there is something interesting about it that I'm missing, I'd definitely like to know.
As if you could separate Unix from C. Without C there wouldn't have been any Unix to become popular, there wouldn't have been any coattails to ride.
C gave Unix some advantages that other operating systems of the 1970s and 80s didn't have:
Unix was ported to many different computers spanning a large range of cost and size, from microcomputers to mainframes.
In Unix both the operating system and the applications were written in the same language.
The original Unix and C developers wrote persuasive books that taught the C language and demonstrated how to do systems programming and application programming in C on Unix.
Unix wasn't the first operating system to be written in a high-level language. The Burroughs OS was written in Algol, Multics was written in PL/I, and much of VMS was written in BLISS. None of those languages became popular.
IN the 1970s and 80s, Unix wasn't universal in universities. Other operating systems were also widely used: Tenex, TOPS-10, and TOPS-20 on DEC-10s and 20s, VMS on VAXes. But their systems languages and programming cultures did not catch on in the same way as C and Unix.
The original Macintosh OS of the 1980s was no competitor to Unix. It was a single user system without integrated network support. Apple replaced the original Macintosh OS with a system based on a Unix.
Of course, they weren't available as free beer with source tapes.
> Apple replaced the original Macintosh OS with a system based on a Unix.
Only because they decided to buy NeXT instead of Be.
Had they bough Be, that would not been true at all.
I think this was less important then, than people sometimes think.
I recall those days. In the 1980s and 90s I worked as a scientific programmer in a university department. Some of our software was commercialized and sold and supported as a product for a time in the 80s. Pardon the following long memoir, but I think some reporting on what actually happened then, as seen by even one participant, is pertinent.
We used a VAX with DEC's VMS operating system. Our application was developed in DEC Pascal (which didn't have the limitations of Standard Pascal because it used the DEC CLR, Common Language Runtime). Later on we began using Allegro Common Lisp for some things.
Through the 80s and early 90s, we never used Unix and C. And, we were not unusual, even in a university. Most of the VAXes at that university ran VMS (or one of the DEC-10/20 OS in the early 80s), including the computer science department (which began running Unix on some but not all systems later in the 80s). So Unix was not as pervasive in the 80s as some people seem to think.
About "free beer": running Unix on a VAX in the 1980s was definitely not "free", it was a major investment in time, effort, and yes, money (in the form of salaries). First, the OS wasn't a separate line item. You bought a bundled system including both the VAX hardware and the VMS OS. Then the DEC guy came and turned it on and it just worked. I don't even know how buying a bare VAX and installing your own OS worked. How did you handle DEC field service? They required their own utilities that ran on VMS. If you used Unix, you needed an expert in Unix to install it and maintain it.
And it was no different with the early commercial Unixes. You bought a Sun workstation and it came with their Unix bundled (Solaris or whatever). In the 1990s we switched from VAX/VMS to HP workstations that bundled HP-UX, their Unix. In all of these Unix platforms, Unix was bundled and you did pay for it, it was just included in the price.
I think there is some confusion about the history. The free, frictionless, install-it-and-run-it-yourself OS was not Unix in the 80s, it was Linux in the 1990s. By then C and Unix-like operating systems were well established.
Also, there was genuine admiration for Unix technical features, notably its simplicity and uniformity, even at sites like ours that didn't use it. There were several projects to give VMS a Unix-like userspace. There was a (yes) free Software Tools project (that was its name), and a commercial product called Eunice. People who had already paid for VMS paid more for Enunice to make VMS look like Unix.
Unix was a better platform for teaching CS than VMS or the other alternatives.
VMS did come with source code. It came on a huge stack of fiche cards, along with several pallet-loads of hardcopy documentation in binders.
There was nothing like the books The C Programming Language by K&R, or The Unix Programming Environment by Kernighan and Pike. Or the many Unix and C books that followed them. And then the college courses that used them.
Instead there were special courses in system programming and OS internals (separate courses) from DEC. The university would pay for them once in a while. A DEC expert would come for a week and programmers from all the VAX sites would get together all day every day in a classroom while they lectured. There was no textbook, but everyone got a huge binder of printed notes.
So systems programming on VMS, and I suppose other non-Unix platforms, remained an esoteric, inaccessible art, totally divorced from application programming, that used a programming language that was not used for anything else.
A few words comparing my experience programming in C in the 1990s to programming in DEC Pascal in the 80s: C wasn't much worse. The greater safety of Pascal did not make much difference in application programming. In Pascal, array-bounds errors etc. produced a crash with a traceback. In C similar errors produced a crash with a cryptic message like "segfault". But often the actual defect was far from the line that crashed, that appeared in the traceback, so the investigation and debugging was similar in both languages. But the more common (and often more difficult) errors that just computed the wrong answer were about the same in both languages.
Then the students needed access to lex/yacc etc for their courses and X Windows too.
That we produced other Unix programs was just an artifact of the original drive to have Unix. The Compaq 386 or Macintosh II were niche products for that job and VMS had been turfed by the late eighties.
To me it looks like there is a better way to do things and the better one eventually wins.
From page 52:
> Operating systems have to deal with some very unusual objects and events: interrupts; memory maps; apparent locations in memory that really represent devices, hardware traps and faults; and I/O controllers. It is unlikely that even a low-level model can adequately support all of these notions or new ones that come along in the future. So a key idea in C is that the language model be flexible; with escape hatches to allow the programmer to do the right thing, even if the language designer didn't think of it first.
This. This is the difference between C and Pascal. This is why C won and Pascal lost - because Pascal prohibited everything but what Wirth thought should be allowed, and Wirth had far too limited a vision of what people might need to do. Ritchie, in contrast, knew he wasn't smart enough to play that game, so he didn't try. As a result, in practice C was considerably more usable than Pascal. The closer you were to the metal, the greater C's advantage. And in those days, you were often pretty close to the metal...
Later, on page 60:
> Much of the C model relies on the programmer always being right, so the task of the language is to make it easy what is necessary... The converse model, which is the basis of Pascal and Ada, is that the programmer is often wrong, so the language should make it hard to say anything incorrect... Finally, the large amount of freedom provided in the language means that you can make truly spectacular errors, far exceeding the relatively trivial difficulties you encounter misusing, say, BASIC.
Also true. And it is true that the "Pascal model" of the programmer has quite a bit of truth to it. But programmers collectively chose freedom over restrictions, even restrictions that were intended to be for their own good.
"A consequence of this principle is that every occurrence of every subscript of every subscripted variable was on every occasion checked at run time against both the upper and the lower declared bounds of the array. Many years later we asked our customers whether they wished us to provide an option to switch off these checks in the interests of efficiency on production runs. Unanimously, they urged us not to they already knew how frequently subscript errors occur on production runs where failure to detect them could be disastrous. I note with fear and horror that even in 1980 language designers and users have not learned this lesson. In any respectable branch of engineering, failure to observe such elementary precautions would have long been against the law."
-- C.A.R Hoare's "The 1980 ACM Turing Award Lecture"
In my opinion it is only Rust that offers a language without runtime system requirement and fixes essentially all of the problems of C.
C has a runtime, even if tiny. That is what calls into main(), handles floating point arithmetic when none is available, functions that run before and after main(), nowadays also does threading.
Heap memory handling in Pascal, Modula-2, Ada, is much safer than C, first of all no need to do math to calculate the right size, arenas are available on the standard library, dinamic allocation can also be managed by the compiler if desired (Ada), pointers are safe as they by default must be used with existing data, however if one really wants to do pointer arithmetic it is available.
The only issue that they have in regards to C, is the use-after-free, but that apparently isn't an issue for folks moving away from C into Zig, wich is basically Modula-2 with some C syntax flavour.
There is a quite a bit of C code that makes creative use of the size of allocations. For example linked lists with a variable sized payload. Again one of the things that would prevent a C programmer from switching to Pascal.
I don't expect the Zig user base to become larger than the Rust user base any time soon. But we have to wait and see, Zig is quite young.
Creative use of the size of allocations are also possible in those languages, the BIG difference is that they aren't the default way everything gets done.
(I know quite well that C couldn't. Even a C++ vector may or may not, depending on which access method you use.)
C totally accepts breaking the third wall. And Pascal doesn't.
Problem with C currently is the language is controlled by people that think programming languages should be like Pascal.
I mean Unix had to be written in C, not in, say, Algol or PL/I or BLISS, high-level languages used to write other operating systems.
I also meant that the features of C were not put there by impulse or whim, they were the outcome of considered decisions guided by the specific needs of Unix.
> Although we entertained occasional thoughts about implementing one of the major languages of the time like Fortran, PL/I, or Algol 68, such a project seemed hopelessly large for our resources: much simpler and smaller tools were called for. All these languages influenced our work, but it was more fun to do things on our own.
> Although we entertained occasional thoughts about implementing one of the major languages of the time like Fortran, PL/I, or Algol 68, such a project seemed hopelessly large for our resources: much simpler and smaller tools were called for. All these languages influenced our work, but it was more fun to do things on our own.
https://www.nokia.com/bell-labs/about/dennis-m-ritchie/chist...
Pity that in regards to secure programing practices in C, community also ignores the decisions of the authors.
> Although the first edition of K&R described most of the rules that brought C's type structure to its present form, many programs written in the older, more relaxed style persisted, and so did compilers that tolerated it. To encourage people to pay more attention to the official language rules, to detect legal but suspicious constructions, and to help find interface mismatches undetectable with simple mechanisms for separate compilation, Steve Johnson adapted his pcc compiler to produce lint [Johnson 79b], which scanned a set of files and remarked on dubious constructions.
Also to be noted that on Plan 9 they attempted to replace C with Alef for userspace, and while the experiment failed, they went with Limbo on Inferno, and also contributed to Go.
And that C compiler on Plan 9 is its own thing,
> The compiler implements ANSI C with some restrictions and extensions [ANSI90]. Most of the restrictions are due to personal preference, while most of the extensions were to help in the implementation of Plan 9. There are other departures from the standard, particularly in the libraries, that are beyond the scope of this paper.
https://doc.cat-v.org/plan_9/4th_edition/papers/compiler
1. Conservation of mass: the amount of C code you put in will be pretty close to the amount of machine code you get out. Aside from the preprocessor, which is very obviously expanding macros, there are almost no features of C that will take a small amount of code and expand it to a large amount of output. This makes some things annoyingly verbose to code in C (eg. string manipulation), but that annoyance is reflecting a true fact of machine code, which is that it cannot handle strings very easily.
2. Conservation of energy: the only work that will be performed is the code that you put into your program. There is no "supervisor" performing work on the side (garbage collection, stack checking, context switching), on your behalf. From a practical perspective, this means that the machine code produced by a C compiler is standalone, and can be called from any runtime without needing a special environment to be set up. This is what makes C such a good language for implementing garbage collection, stack checking, context switching, etc.
There are some exceptions to both of these principles. Auto-vectorizing compilers can produce large amounts of output from small amounts of input. Some C compilers do support stack checking (eg. `-fstack-check`). Some implementations of C will perform garbage collection (eg. Boehm, Fil-C). For dynamically linked executables, the PLT stubs will perform hash table lookups the first time you call a function. The point is that C makes it very possible to avoid all of these things, which has made it a great technology for programming close to the machine.
Some languages excel at one but not the other. Byte-code oriented languages generally do well at (1): for example, Java .class files are usually pretty lean, as the byte-code semantics are pretty close to the Java langauge. Go is also pretty good at (1). Languages like C++ or Rust are generally good at (2), but have much larger binaries on average than C thanks to generics, exceptions/panics, and other features. C is one of the few languages I've seen that does both (1) and (2) well.
In a nutshell, the useful fiction of computer-as-Von-Neumann-meaning doesn’t adequately reflect the reality of modern hardware. Not only does the CPU itself not fit that model (with things like speculative execution, sophisticated power and load management…), but the system as a whole is increasingly an amalgamation of different processors and address spaces.
Take the "wobbly types" for example. It would have been more "minimal" to have types tied directly to their sizes instead of having short, int, long, etc.
There isn't any reason that compilers on the same platform have to disagree on the layout of the same basic type, but they do.
The complaints about parsing header files could potentially be solved by an IDL that could compile to c header files and ffi definitions for other languages. It could even be a subset of c that is easier to parse. But nothing like that has ever caught on.
This hasn't been true for decades. x86 assembly is now itself an abstraction over what the CPU is actually doing.
Microcode, speculative execution, etc.
For example, C has pointer provenance, so pointers arent just addresses. Thats why type punning is such a mess. If a lang claims to be super close to the hardware this seems like a very weird thing.
What makes C feel free for programming is that instead of prescribing an implementation paradigm, it instead exposes a computing model and then lets the programmer write whatever is possible with that (and also what is not -- UB). And a lot of higher level abstractions are quickly implemented in C, e.g. inheritance and polymorphism, but then they still allow to be used in ways you like, so you can not just do pure class inheritance, but get creative with a vtable, or just use another vtable with the same object. These are things you can't do when the classes are a language construct.
My gripe is only with people acting like the C abstract machine doesn't exist and C is just syntax sugar for a bit of assembly. It's a bit more involved than that.
Exactly.
Everything in assembly is still one-to-one in terms of functional/stateful behavior to actual execution. Runtime hardware optimization (pinhole instruction decomposition and reordering, speculative branching, automated caching, etc.) give a performance boost but do not change the model. Doing so would mean it didn't work!
And C is still very close to the assembly, in terms of basic operations. Even if a compiler is able to map the same C operations to different instructions (i.e. regular, SIMD, etc.)
If language extensions to ISO C are allowed, then same goes for my selection on competing systems languages.
Java with only primitive values, arrays, and classes only with fields and static methods.
But that wouldn’t be idiomatic Java, so typically non-explicit abstractions such as polymorphism have code generated for them that you don’t have explicit control over.
C is consistently low level because that’s all you get. Down to direct access to addressing and RAM, the stack frame, etc. as with assembly.
Being idiomatic or not doesn't matter, what counts are what language features are available.
Note here “close” being used in the injective, not bijective, sense. (Scratch out “one-to-one” in my earlier comment.)
And “closer” lowers the bar here too. C isn’t simply decorated assembly. But closer to it.
And “close” being used informally. Arguments for closeness are several and strong (I think), but a bit of a hodgepodge.
In terms of non-bijectivity, for systems programming and performance choices C makes it easy to drop into assembly. But the former are uniquely application specific. And the latter doesn’t make the C version less like the assembly it maps onto - whether the compiler uses the more performant instructions for the context or not.
C’s convenient assembly inlining, and the handoff in both directions being smoothed by an assembly friendly model of the C code around it, are both a part of the “closeness”
But C is generally “close” to assembly, because its data types emphasize types handled natively, compound types reflect RAM layout, and pointers are explicit addresses to data and code. And those address values can be constructed and operated on just like any other data.
C is objectively closer to assembly than languages with strongly required abstractions. (E.g., Java classes, Lisp S-exp's/cons cells, etc.)
C is more “strictly closer” to assembly than languages with more optional abstractions, even if they also allow for relatively low level coding.
Functions could be viewed as a preferred abstraction, but they have a clear assembly level model accessible directly with pointer arithmetic. And they don’t get in the way of directly encoding custom argument passing schemes, and using goto’s and zero argument functions and tail calls as atomic assembly calls for function and jumps for continuations.
Types are a significant non-assembly abstraction, but are zero-cost in that they don't separate C from assembly, but C from C, as a code safety mechanism that is easily escaped.
It is often easy to add abstractions, via regular C, or macros, but you have to provide an explicit implementation for them in the source or complied library.
(However, if macros, with their mixed logical, symbol, text and file “data” model, are viewed as C source instead of as a C source construction language, then C becomes a very wacky abstraction language with behavior and rules that look nothing like simple assembly.)
The manipulation of memory by C programs is close semantically to the manipulation of memory by assembly programs. Memory accessed through pointers is similarly "external" to both assembly language and C programs.
The evaluation of C program code is not close to assembly language. C programs cannot reflect on themselves portably; features like parameter passing, returning, and allocating local storage during procedure activation, are not in the programming model.
C loses access to detailed machine state. Errors that machine language can catch, like overflows, division by zero and whatnot, are "undefined behavior". An assembly language program can easily add two integers together and then two more integers which include the carry out from the previous addition. Not so in C.
Assembly language instruction set designs (with some exceptions) tend to bend over backwards to preserve the functioning of existing binary programs, by maintaining the illusion that instructions are executed in sequence as if there were no pipelining or speculative execution, or register renaming, etc.
Meanwhile, C compiler vendors bend over backwards to prove that code you wrote 17 years ago was wrong and make it fail. C is full of unspecified evaluation orders and various kinds of undefined behavior in just the basic evaluation model of its syntactic, built-in constructs; and then some more in the use of libraries.
In assembly language, you would never have doubt about the order of evaluation of arguments for a procedure.
Even when it comes to memory, where C and asasembly language agree in many points, there are some subtle ways C can screw you. In assembly language, you would never wonder whether copying a structure from one memory location to another included the alignment padding bits. In C you also don't have to wonder, if you use memcpy. Oh, but if you use memset to clear some memory which you don't touch afterward and which goes out of scope, the compiler can optimize that away, oops!
Did anyone say that? I think the point is not that it is not "close", but that C is not equivalent to ASM: C has its own abstractions, and there are things you can do on assembly that you can't express in C.
The other low level languages such as C++, Rust, Zig, ... are equally close since you can express the same things. In some respect they are even closer since they got more features builtins that modern assembly can now do that was not part of the design in C. (SIMD, threading, ...)
Modern languages also have extra abstractions that makes programming easier without compromising on the cost. There are more abstractions than in C, but they also are optional. (Just like you could use goto instead of while or for loop, but you're happy this abstractions exist. We could also use functions pointer in C++ instead of virtual functions, but why would we if the language provide tools that make programming easier, for the same result)
> The other low level languages such as C++, Rust, Zig, ... are equally close since you can express the same things.
C is not just low level friendly, but low level out of the box. That is the level that all C must be written in, even when creating higher abstractions.
Some higher level languages are also low level friendly, not low level strict. Which is a kind dual.
I would argue that what makes C lower level, is that it comes in at, or under, the low levels of other languages, and its high bar comes in much lower than the abstractions built into other languages.
Forth is a good candidate for being even lower level.
But if someone else doesn't see things that way, that is fine. It is just one lens for comparing languages.
No, it is not:
- People use for/while loop, for example, instead of the "low level" 'goto'
- C compiler compute pointer aliasing, assume operations don't overflow, etc., in order to optimise your code: What you write doesn't translate directly to assembly.
- Some low level operations cannot even be represented in pure C (without using __asm__ extension escape hatch)
It's 2026, to this date I cannot use standard library/api, to open a file with utf-8 filename without a null terminating string.
When you want to talk to the OS you constantly face the need to had unnecessary overhead (allocation due to string convertion, strlen).
The OS itself does not prevent anything from having those standard "no overhead" API.
However, it's pretty clear that nobody cares to define some new sane interface and nobody care to deprecate old ones.
That would include both API and ABI.
And then the gain is close to zero because most filenames are short enough that there is almost no gain.
You need to do weird string operations, you have certainly a class somewhere that needs to append a zero to then end of a buffer, and exclusively use the class for thw filename. You can't just toss a contiguous number of bytes you to convert it first.
Every single piece of software that need to interact with the file system needs to deal with this.
I'm not asking about a new string type. I'm asking to be able to be free from null terminating string.
You only need to provide a length.
https://dl.acm.org/doi/abs/10.1145/3133850.3133867
Not everything has to be written with all the warmth and humanity of a UN subcommittee interim report on widget standardisation.
Choose your own adjective
But Rust evangelists cannot stop people from using any particular language
Assembly is the only language that matters. It's the only language the computer "understands"
Everything other language is just an abstraction over it
When it comes to abstractions not everyone has the same preferences
- unspecified default type sizes. Should have had i8, u16, i32, u64, f32, f64 from the beginning.
- aliasing pointers being restricted by default (ie an alias keyword should have been added). Performance matters. All these benchmarks which show something beating C or C++ are mostly due to dealing with aliasing pointers. C++26 still doesnt have standardised restrict keyword.
There are more but I understand the logic/usability/history behind them. The above points should have been addressed in the 80’s.
As you said, it's easy to see where it came from, but it should've been fixed long ago.
Is 27 years for you not long ago enough? That's more than a generation away and closer to the invention of the language than today.
https://www.nokia.com/bell-labs/about/dennis-m-ritchie/varar...
C is sin incarnated.
> The barebones-macro-system?
The CPP is a different language and designed that way so you can use another language that suits you better, most don't do that, because the default is fine.
You simply choose the integer type that your problem or task requires. If the hardware genuinely can‘t cope with it (performance), you reevaluate your requirements, define new constraints and choose new types. This is basic requirements engineering, which C only made more convoluted.
For number of elements there's size_t. So again, when are "natural types" the right answer?
I strongly disagree. The programmer should rather prescribe intent and shouldn't constantly think about what size this should exactly have. Does this variable represent the size of an object, an address, a difference or just a random positive integer? Then use size_t, uintptr_t, ptrdiff_t and unsigned int respectively. Why should I care what exact sizes these are? I hate that modern (system) languages completely throw away that concept. "I want a unsigned integer" "oh, you mean u32" "No! I really mean an unsigned integer."
Also when I do want e.g. 32 bit available, there is no reason, I need to use a suboptimal 32 bit wrapping behaviour when I don't need it. The correct type to use for computation for that would be uint32_fast_t and the implementation chooses what makes sense to use for this.
Choosing the right type is a function of signedness, upper/lower bound (number of things), and sometimes alignment. These are fundamental properties of the problem domain. Guesstimating is simply not doing the required work.
If you target about 32bit and 16bit, you either think about and using long (on 16 bit is more costly) or you just count seconds.. Or ticks.. or whatever you need.
If your decision is that you really want to store all 32-bit values then you use uint_least32_t or uint_fast32_t depending if you are also resource constrained.
It's understandable how we got here, but it's an entirely legitimate question - could things be better if we had an explicitly designed interoperability interface? Given my experiences with cgo, I'd be pretty solidly on the "Fuck yes" side of things.
(Of course, any such interface would probably end up being designed by committee and end up embodying chunks of ALGOL ABI or something instead, so this may not be the worst possible world but that doesn't mean we have to like it)
[1] I absolutely buy the argument that HTTP probably wins out for out of process
Also, if I remember correctly, the first Rust and Go compilers were written in C.
Usually it helps to know why some decision was taken, it isn't always because of the technology alone.
Sure history is great and all, but in C it's hard to say reliably define this int is 64-bit wide, because of the wobbly type system. Plus, the whole historical baggage of not having 128-bit wide ints. Or sane strings (not null terminated).
That isn't really a problem any more (since c99). You can define it as uint64_t.
But we have a ton of existing APIs that are defined using the wobbly types, so we're kind of stuck with it. And even new APIs use the wobbly types because the author didn't use that for whatever reason.
But that is far from the only issue.
128 bit ints is definitely a problem though, you don't even get agreement between different compilers on the same os on the same hardware.
It kinda is. Because it was made in the 1970s, and it shows (cough null-terminated strings uncough).
Or you know having a 64-bit wide integer. Reliably.
You did read the article, right?
Yes, we could define a language-agnostic binary interoperability standard with it's own interface definition language, or IDL. Maybe call it something neutral like the component object model, or just COM[1]. :)
[1] https://en.wikipedia.org/wiki/Component_Object_Model
It’s not just C. There are a lot of things that could be substantially better in an OS than Linux, for example, or in client-server software and UI frameworks than the web stack. It nevertheless is quite unrealistic to ditch Linux or the web stack for something else. You have to work with what’s there.
Verilog is loosely based on C. Most designs are done in Verilog.
Other than that, Nokia until Microsoft placed an agent on it, Phillips that contributed to CDs, ASML...
There's an argument for full type info at an API, but that gets complicated across languages. Things that do that degenerate into CORBA. Size info, though, is meaningful at the machine level, and ought to be there.
Apple originally had Pascal APIs for the Mac, which did carry along size info. But they caved and went with C APIs.
There has to be an ABI that has to be agreed upon by everyone. Otherwise there wouldn’t be any interoperability. And if we didn’t have the SystemV ABI — what would we use instead? Prepare for a long debate as every language author, operating system designer, and platform under the sun argues for their respective demands and proposals. And as sure as the sun rises in the East someone, somewhere, would write an article such as this one decrying that blessed ABI.
SystemV shouldn’t be the be all and end all, IMO. But progress should be incremental. Because a lingua franca loses its primary feature and utility when we all return to our own fiefdoms and stop talking to one another in the common tongue.
It’s a pain in the metaphorical butt. But it’s better, IMO, than the alternatives. It’s kind of neat that SystemV works so well let alone at all.
Good read though. Thinking about C as not just a language but also a protocol is a different perspective that is useful for the mental model.
The whole world shouldn't "need to be fixed" because you won't spend the time to learn something.
Rust doesn't even have a stable Internal ABI that's why you have to re-compile everything all the time.
The System V ABI is as close as we get to an actual specification but not everyone uses it and in any case it only covers a small part of the protocol.
> I’m trying to materially improve the conditions of using literally any language other than C.
"In the matter of reforming things, as distinct from deforming them, there is one plain and simple principle; a principle which will probably be called a paradox. There exists in such a case a certain institution or law; let us say, for the sake of simplicity, a fence or gate erected across a road. The more modern type of reformer goes gaily up to it and says, "I don't see the use of this; let us clear it away." To which the more intelligent type of reformer will do well to answer: "If you don't see the use of it, I certainly won't let you clear it away. Go away and think. Then, when you can come back and tell me that you do see the use of it, I may allow you to destroy it.""
-G.K. Chesterton
#1: What you consider a "language" is one with its own official runtime. By your logic, Javascript is not a language either because the interpreter is usually written in C++ (e.g. V8, spidermonkey).
#2: Dogfooding a compiler and a runtime is common practice because most people believe that it will help the makers of a language identify bugs in their implementation or identify undefined behavior in the very definition of their language. However, every language must bootstrap from another language: most languages bootstrapped from a compiler written in C. If you trace the lineage from compiler to compiler to ... compiler, you'll most likely find that the second C compiler ever written was written in C and the first compiler was written in some architecture's assembly language. The same still applies for the new, new language of C99 which is less than half the age of C. C is so mature that rooting out new undefined behaviors is no longer a significant concern.
#3: libc is not just a "language runtime", it's the interface into the OS kernel for most operating systems. The runtime for every "real language" as you describe it ultimately uses libc. It doesn't make sense to separate the functions in the C99 standard from the OS's own syscalls and special-purpose user land functions call C standard library functions, and C's standard library relies on the OS's special functions. If they were separate libraries, there would be a circular dependency.
Think of C not as "not a language" but as a language that serves the unusual purpose of being the foundation for nearly everything else.
You have a point on integer types being a mess, but we have stdint.h for that.
That's exactly my case. For my programming language I have wrote a tool for C headers conversion using libclang. And even with help of this library it wasn't that easy, I have found a lot of caveats by trying converting headers like <windows.h>.
The real protocol in action here is symbolic linking and hardware call ABIs.
You could always directly call Rust functions, but you'd have to know where to symbolically look for them and how to craft its parameters for example.
If this is well defined then its possible. If its poorly defined or implementation specific (c++) then yeah its a shit show that is not solvable.
With C++ it's the same. Within the Haiku code it's half understandable, the whole spec it's to get driven mad in days.
Eg. here, from memory:
> ...you want to read 32 bits from file but OH NOOES long is 64 bit ! The language ! The imposibility !
But when you read something ot unserialize some format you just need to know based on format schema or domain knowledge. Simple and straightforward like that ! You do not do some "reflections" on what language standard provide and then expect someone send you just that !!
So that anti-C "movement" is mostly based on brainless exampless.
Not saying C is perfect.
But it is very good and I bet IBM and other big corps will keep selling things written and actively developed in C/C++ + adding hefty consulting fees.
In the meantime proles has been adviced to move to cpu-cycle-eating inferior languages and layers over layers of cycle burning infra in cloud-level zero-privacy and guaranteed data leaks.
Oh, btw. that femous Java "bean" is just object with usually language delivered "basic type"... How that poor programmer from article should know what to read from disc when he just have types Java provides ?? How ? Or maybe he should use some domain knowledge or schema for problem he is trying to solve ??
And in "scripting language" with automatic int's - how to even know how many bits runtime/vm actually use ? Maybe some reflection to check type ? But again how that even helps if there is no knowledge in brain how many bits should be read ?? But calling some cycle burning reflection or virtual and as much as posible indirect things is what fat tigers love the moust :)
We didn't do it to annoy you or to foist bad APIs on you. We did it because it was the best language for writing machine code at the time. By miles. Not understanding why this is true will lead you to make all the same mistakes the languages "bested" by C made.
There's some people that still develop on C for sure, but it's limited to FOSS and embedded at this point, Low Level proprietary systems having migrated to C++ or Rust mostly.
I agree with the main thesis that C isn't a language like the others, something that we practice, that it's mostly an ancient but highly influential language, and it's an API/ABI.
What I disagree with is that 'critiquing' C is productive in the same way that critiquing Roman Law or Latin or Plato is productive, the horse is dead, one might think they are being clever or novel for finding flaws in the dead horse, but it's more often a defense mechanism to justify having a hard time learning the decades of backwards compatibility, edge cases and warts that have been developed.
It's easier to think of the previous generation as being dumb and having made mistakes that could have been fixed, and that it all could be simpler, rather than recognize that engineering is super complex and that we might as well dedicate our full life to learning this craft and still not make a dent.
I applaud the new generation for taking on this challenge and giving their best shot at the revolution, but I'm personally thinking of bridging the next-next generation and the previous generation of devs, the historical complexity of the field will increase linearly with time and I think if we pace ourselves we can keep the complexity down, and the more times we hop unto a revolution that disregards the previous generation as dumb, the bigger the complexity is going to be.
I fully agree about your last point. The proposed solutions to some of the deficiencies of C are sometimes worse than the disease while its benefits are often exaggerated, at the same time adding unnecessary layers of complexity that will haunt us for decades. In contrast, my hope would be to to carefully revise the things we have, but this takes time and patience.
Do you mean that there's still code being developed, or that it still exists? Because I meant the former, the latter is true of a lot of dead languages like fortran and cobol, and it would cement the idea of being a dead language.
Being upfront about it by authors dispels a lot of the potential tension and substantially changes the way we interact. I understand there may be a conflict and not everyone will want to advertise their diagnosis, but in my experience once it becomes clear that's what's going on, it helps all the parties involved.