A * B;
Is it A multiplied by B, or B declared as a pointer to A? It can't be resolved without a symbol table. But there's another way that works very well: it's a declaration. The reason is simple. The multiply has no purpose, and so nobody would write that. C doesn't have metaprogramming, so it won't be generating such code as an edge case (unless using the preprocessor for metaprogramming, in which case you deserve what you get).But there's a worse problem:
(A) - B
Is it A minus B, or casting negative B to type A? There's just no way to know without a symbol table. One might think who would write code that has a vacuous set of parentheses around an identifier? It turns out they don't, but they write macros that parenthesize the arguments, and the preprocessed result has those vacuous parentheses.D resolves both issues with:
1. if it parses like a declaration, it's a declaration
2. a cast expression is preceded by the keyword `cast`
and D is easy to parse without a symbol table.
But there's another way that works very well: it's a multiplication. The reason is simple. A declaration of a variable that isn’t used has no purpose, and so nobody would write that.
As I think you know, C doesn’t handle this case by guessing that it must be a declaration. Its lexer looks in the tables that its parser creates to check whether a type called ‘A’ is in scope (https://stackoverflow.com/questions/41331871/how-c-c-parser-...)
A fellow named Bjarne Stroustrup fixed that bug, though. In the plus plus dialect of C, A * B could reboot your system, without any #define macros for A or B.
Lol at this. Love this eloquent style, and pretty much agree. However, C's reign is not absolute. Virgil compiles to tiny native binaries and runs in user space on three different platforms without a lick of C code, and runs on Wasm and the JVM to boot. I've invested 12+ years of my life to bootstrap it from nothing just to show it can be done and that we can at least have a completely different userspace environment. No C ABI considerations over here. It can be as romantic as just you and the kernel in Virgil land. Heh.
That's the real problem the author is ranting about. If you've solved that, too, then I think that the author's article isn't the only Rust team member who'd like to talk to you.
Definitely looks like a cool project. Can you write an OS in it, for microcontrollers ?
- You have to speak the C ABI to talk to the OS -- yes, if you want an OS to support more than one language, or you want multiple languages within a single process, you need a common ABI of some sort. What is the alternative that doesn't involve a common ABI?
- C has too many ABIs -- yes, there are a lot of different kinds of hardware, and sometimes multiple software ecosystems evolved in parallel on the same hardware (eg. Windows and Linux). What is the alternative?
- ABI changes are hard to make in a non-breaking way -- yes they are. It's fundamentally a hard problem. You could compile everything from source every time, but the open-source world seems to have decided that there isn't enough time or CPU power to "live at head" and build from source every time, like Google does: https://abseil.io/about/philosophy#we-recommend-that-you-cho... (Disclosure: I work at Google).
- "You Can’t Actually Parse A C Header" -- this is, IMO, the most actionable objection that the article raises. You could imagine a subset of C that does away with macros, typedefs, etc. in order to be easier to parse, and in doing so forms a more accessible ABI specification language. That seems eminently doable. But parsing is only part of the overall battle: actually implementing the ABI for 176 triples seems like the bigger problem. Especially when you add the ABI-impacting function attributes mentioned in the tweet.
As a C fan, it's hard to see the language itself blamed for what are (in my view) just fundamentally difficult problems.
> You have to speak the C ABI to talk to the OS [...] What is your alternative that doesn't involve a common ABI?
Emphasis is on the _C_ ABI. The alternative is something that at least tries to be designed as a universal ABI.
> there are a lot of different kinds of hardware, and sometimes multiple software ecosystems evolved in parallel on the same hardware
If there was an explicit ABI standard though, evolving ecosystems would have to go through the standardization and thus you just couldn't get conflicting ABIs for the same thing.
If such standardization could cut the number of ABIs in half, it'd still be a huge win. And I think it'd be possible to go much further than that. Furthermore, it may be possible to split orthogonal things apart, such that instead of e.g. 50 targets, you have 5 OS targets and 10 arch targets, each specifying a separate part of the ABI. (current ABIs obviously do this to some extent, but not in any specified & structured way)
> As a C fan, it's hard to see the language itself blamed for what are (in my view) just fundamentally difficult problems.
I also like C. I agree these are hard problems. But I don't think they're problems that C should be dealing with. But we're pretty much just stuck with C.
You don't need to parse C headers in order to use the same ABI. You can read DWARF info which is designed to convey type definitions, function/method prototypes etc. in a machine-readable way. It's already there, no need for a separate "IDL" standard at all.
C isn’t ideal, but it’s actually not so bad and it could be worse (it could be C++). Yes parsing C is non-trivial, but that’s true of every language. These days you have libclang and a dozen other decent C parsers. Back in my day we had to use a hacked up version of GCC (and we liked it).
Also, C doesn’t have a standard ABI, but every real world platform defines a C ABI. And it’s pretty simple. Meanwhile trying to handle all of the cases of C++ vtables took up weeks of my life (and I ended up shipping without fully supporting multiple inheritance, which is stupid anyway).
The bigger problem for writing FFIs is, in my opinion, memory management. That’s where it gets really hard to paper over for the binding user that you’re talking to C.
Of course it doesn't. C implementations do. This isn't really any different than most other languages, but feels different because C doesn't have a blessed implementation that all other implementations must interact with.
That's a strength. It means C is found on esoteric microcontrollers as well as powerful modern desktops. That wouldn't work as well as it could if the ABI were uniform on all targets and implementations.
And yes, it can act as a protocol. It's the simplest way to access host ABI communication without understanding it.
Of course, the POSIX-likes never adopted this, whereas Microsoft nowadays has some fourth generation of this IDL stuff (WinMD) that they are also slowly porting all the old C API definitions to (see win32metadata, also used for defining stuff like the Win32 package for Rust).
Also, of course, this all has its own issues too, for one COM's definition of reference counting is a bit picky, and there were a lot of advanced 'implicit RPC' features that were also more inherent footguns, but at least it doesn't involve what is ranted about here.. mostly.
I work on an implementation of a high-level language (implemented in C) that probably less than 100 people have used, and a C FFI has been asked for plenty of times. You can't get around it.
Would some thing other than C being the ABI be better? Who knows. But the current situation just sucks.
edit: I'd like to explicitly note that I like C as a language. But it still makes for a bad ABI, because it wasn't meant to be one, and barely even works as one.
The issue is that there isn't really an alternative. Obviously a cross platform ABI is never going to exist (different endianness, alignment, register usage, stack conventions just considering the CPU, OSs themselves add more complexity). We could have an IDL to describe in details the ABI, but then:
- you need all OS vendors to be on board, which is not going to happen.
- even if they did you can be sure it will be forked in a myriad of dialects and non conforming implementations.
- even if everybody plays ball, bugs will still happen.
The best next is for language designers to come up with community maintained IDLs and tooling to interface with various languages instead of waiting for platform vendors to provide them. This is a realistic solution that can work, but at this point you might just accept that C fulfils this role already even if it is far from ideal. Just embrace libclang and hold your nose.
edit: there is also the option of targeting a single virtualized platform like the JVM or CLR which is great, but not really appropriate for a system language.
As I read it, the author's thesis is that all programming languages have to talk to the operating system and the operating system is written in C so the operating system uses C calling conventions which leaks C's "ugliness" into the implementation or expression of their beautiful language.
I kind of think of this as the "I like computers but don't really understand computation" fallacy. It is fundamental lack of understanding about the nature of computer architectures and what they can and cannot do vis-a-vis how you might express that in a programming language.
One of my professors in college was fond of saying that "All programming languages are just syntactic sugar around machine code." Which is fundamentally true, and tries to capture that at the end of the day what ever your language "says" has to be expressed in machine code to actually do what it does.
You will spend a lot of time in this space writing a compiler, and code generation is an art all of its own.
But you can side step, a bit, by not writing a compiler, and instead writing an interpreter. The series of articles that were posted here gave a good intro, and while you still have to do the "naughty bit" where you write code in some compilable language that can pretend to be a computer of some different form, you can make everything look like your language.
I always encourage people who are "learning computers" to actually write a compiler (there are some good starting points for that but online courseware from MIT and other sources can get you the lecture material too). Doing that helps broaden one's perspective of what language designers and implementers are up against with regards to pretty much every computer working the "same" way (Von Neumann or Harvard architecture wise)
Memory safety etc is a complaint about C but worrying about ABI breaking is literally what you'll always get when you do anything other than assembly[0]. The author laments that Rust and Swift must speak to C but that isn't because of the K&R controlled cabal, albeit it might have been the initial reason. Today the reason everything must talk to C is because operating systems are written in C and expose their API and ABI in C. Write an OS in Rust or Swift (lol) and then get mass adoption and then you won't have to worry about interfacing with C anymore.
They do eventually touch on that, but as long as OS'es are in C then you need C. There unfortunately is no alternative.
[0] I mean technically, you have "ABI breaks" in ASM too, it's just the program goes it's merry way being zombie like until a seg fault happens or worse.
What this article fails to capture is the fundamental question that is faced any time a new architecture is encountered: should 'int' be sized (number of bits) according to its original (or most recent) de-facto definition? Or should it be sized according to the natural register size of the architecture's general-purpose registers?
A lot of us went through this back in the mid-2000's when AMD64 (x86_64) came out. It took both Microsoft and the Linux crowd (just to name two communities) time to come up with their respective (and incompatible) translaitons of types. The top answer to this StackOverflow question summarizes this well:
https://stackoverflow.com/questions/384502/what-is-the-bit-s...
In the gaming industry, we came up with our own typedefs until the standards caught up. Things like int_8, int_64, etc. I say only a fool would ever use somehting as pretentious a concept as 'intmax_t' in normal code (which isn't a tranlation table of typedefs based on platform). "max" according to whom? That is not the way.
Who specifies how you talk to the OS, and how native applications talk to each other? The OS does. And it does, in fact, differ across different OSes (with different calling conventions). The only reason C comes into this picture is because it runs on all the platforms these other languages do (and many more), so you can write an adapter between the language and C, and not have to worry about supporting 10 million different calling conventions, because some C compiler author has done that for you.
So the article is half right; this isn't a programming language. But C sure is.
(And that's not to mention the fact that to some extent the ABI and calling convention is determined more by the CPU architecture than the OS, much less the language!)
Ok - I get that. But, there is a system call interface. int 0x80, syscall.
Now, these can be wrapped, and the result exposed in a completely different way. But that is the definition. If its "C" on one side and "C" on the other... well, ok then! I though Linux had vDSOs to allow direct "ABI" calls for performance reasons.... utilizing that will force a certain "C-ish" look. In turn, that can be wrapped. None of this changes quickly.
Heck. CP/M-80 had "CALL 5" with registers a certain way. Wasn't "C" by any stretch!
Because the C (POSIX, mostly) "API" is stable and available, we tend to use it. Wasn't always the case -- after all, FORTRAN I/O was all the rage back in the 60s (cf SNOBOL4).
If (whatever) programming system wants to avail itself of the C infrastructure, it is certainly free to do so. Stop the endless whinging about C! Why C? It is the only language in its class that works from Z80 to my Thinkpad.
On a slightly different topic, few of the problems mentioned in the OP really have much to do with C. I still remember when the interfaces to the original Mac OS (the stuff in Inside Mac, before it got UNIXified) were expressed in Pascal terms. I even vaguely remember MTS interfaces expressed in 360-assembly terms. They were absolutely no better. There is an art to making ABIs and APIs and SPIs and network wire formats and on-disk formats future proof, but it has little or nothing to do with language. The problem is that a bunch of such interfaces exist out there - because they need to exist in concrete form to be useful - that weren't defined with that level of care. It's not much more than coincidence that C happened to be a dominant language in many of those times and domains.
An ABI based on a more "modern" language that involved more detailed types or (even worse) garbage collection would be far far worse for anyone using any other language. C has plenty of problems, but as a notation for expressing an ABI (much like Algol is still sometimes used as a notation for algorithms) it's really not bad.
[1] https://en.wikipedia.org/wiki/Interface_description_language
It's easy to complain about C or C++, but I don't see that many languages that compiles to machine code, and offer compilers for many platforms.
I agree that C is somehow the new "assembly".
C is the glue used for building operating system, so it's not surprising you need to use it in many places.
I'm sorry but I don't see any better language that is as simple to learn for students as C, and fixes some problems of C. Most new languages are often difficult to read, introduce a lot of un-needed sophisticated features, and are not that much used because they're too niche.
A good example is Rust. Sure, the safety is an awesome feature, but ADA already did it somehow, but it's not what developers need.
There is a reason the world talk english and not german or japanese. It's because english is just much, much easier to learn. Nobody cares if the language makes sense.
It's odd because HTML and javascript are so much more ambiguous and cause a lot of pain, yet I hear more complaints about C or C++.
A * B; And (A) - B Can only mean one thing. Sure this wouldn’t do anything for the old code, but at least where new C code was required we wouldn’t continue dealing with all of its complexity.
I feel like this is the same problem with CSVs. It is trivial to parse if people follow the standard but most people don’t. But if you create a parser that only accepts the standard and refuses to parse non standard CSVs then you never have to deal with any of the BS edge cases that come up. And if you show where in the code/CSV the ambiguity is, people might actually fix their code to get it to parse correctly instead of leaving it ambiguous
But the image above that is calling conventions for functions on a particular processor. What does that have to do with C?
> So actually semantically parsing a C header is a horrible nightmare
I'm confused. It seems to me the right way to go would be to improve C such that it's more precisely specified, easier to parse, etc. Genuine question: why is that so hard? (I suspect the reasons are more institutional than technical.)
Variable declarations are syntactically weird in C, for example. And newer languages seem to have better ways (that are actually context-free). So why couldn't C be evolved toward that?
> int foo(int x, int y)
becomes
> foo(x: i32, y: i32) -> i32
and so on.
> You don’t see England trying to improve itself, do you?
I guess that's a saying I'm not familiar with. Seems to me England has improved a lot over the years.
Go easy on me, geniuses.
This is why pure C projects can turn into an unmanageable behemoths. This is also why the best way to use C is to use it to implement core algorithms and data structures which can then be called by higher-level languages. Numpy/Scipy did this perfectly and now their use is now ubiquitous within the Python community.
Most software engineers I know who have a background in EE love C, simply because it maps very well to what a processor actually does during execution.
These things come up in C first because C is the lingua franca.
I submit that this Tower of Babel truth is older than C and a reflection of the humanity that created the tool, not the tool itself.
Minimize the entropy and be at peace with existential imperfection, say I.
Who knows, in the future maybe wasm will be the common ABI between languages.
Sure they can, you just need to build the mechanism that allows them to.
That man page is for glibc. You instead want to look at man syscalls and man syscall to look up how to interface with he kernel. Linux does not require programs to use any amount of C.
If you've ever designed hardware and had to bootstrap it up to a "high level" situation - then these are nonsense concerned when you are merely trying to get a new hardware design up and running. You can always bootstrap to a language that already has all these things. The author obviously has never done anything like that. Not based in any reality where C matters.
Hardly an argument, in my opinion, and it's annoying to see parroted on every thread about C's syntax.
Henry Ford
The compiler also does the ABIs for several targets, even more if you include the LLVM and GCC backends.
C is a shit old language but we have basically tamed it.