C Isn't a Programming Language Anymore (2022)
faultlore.com
faultlore.com
I read until the "FFI" section
Yes, you must use "C" if you use the libc (seems obvious)
However, you can issue your syscalls directly. Of course, to do that, you will have to rewrite your own libc .. nothing is free.
It shall be noted, however, that this is not possible in every environments: I believe that openbsd's code must use the system libc. At the same time, on windows, you must use the provided library. This does not mean the OS library is always C (but this means your new language must do FFI with whatever language is used by the system).
Maybe only Linux allows anybody to issue syscalls directly ?
That is correct. For the reason that Linux is a kernel, and glibc is a separate and independent project, so syscalls are the system interface on Linux.
On other systems things range from syscalls being willfully broken on every update (windows) to syscalls being ABI-stable but removed unless compat-built (FreeBSD), in the middle you have e.g. MacOS which makes no guarantees and will break syscall ABI without warning (even if they’re generally stable) and OpenBSD trying to lock them down to static libc only to mitigate gadgets.
EDIT: You edited your post mentioning this, but I'll leave it here. :-)
Technically most systems do, OpenBSD is the only one I know of actively trying to prevent that.
The line is rather whether there is support and maintenance for doing that aka how likely is it that your program will break on updates.
For Windows, it’s virtually guaranteed because IIRC the syscall numbers are just the index of the syscall name in a big sorted table, so if any syscall gets added or removed the table gets offset and you get the wrong syscall.
The system calling conventions, both register and stack layout/ guarantees, are the major issue.
This creates the contentious issue of Undefined Behavior that C forbids, but which the actual hardware has no problems with.
"undefined behavior behavior, upon use of a nonportable or erroneous program construct or of erroneous data, for which this International Standard imposes no requirements
NOTE Possible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message).
EXAMPLE An example of undefined behavior is the behavior on integer overflow." (The C99 standard, section 3.4.3)
translates into "whatever your CPU does" because while there is not requirement imposed, in general the compiler does make it work "in a manner characteristic of the environment".
I believe that memory accesses outside of array bounds, signed integer overflow, null pointer dereference are all examples of "undefined behaviour", which in practice all boil down to what the CPU does in those cases. I.e. commonly memory access outside of array bounds returns whatever is at that address as long as address is valid because there are no checks and that's what the CPU does when asked to load from address. Integer overflow? If a result of adding/subtracting, commonly it wraps around because that's how the CPU behaves, etc.
And I believe this is all on purpose. C is an abstraction over assembly and I believe that people who were used to their CPU's behaviour wanted to keep it that way in C, and also compilers were simple.
The C security issues that plague us today are partly fueled by the attitude of guesswork and ignorance demonstrated in your posts.
UB has such inexplicable and hard to explain side effects, because all the implicit assumptions in the optimization passes, and symbolic execution used to simplify expressions follow semantics of the C spec, not the details of the specific target hardware.
It creates impossible expectations for the compilers. My recent favorite paradox is: in clang's optimizer comparing addresses of two variables always gives false, because they're obviously separate entities (the hardcoded assumption allows optimizing out the comparison, and doesn't stop variables from being in registers).
But then clang is expected to avoid redundant memcpys and remove useless copies, so the same memory location can be reused instead of copying, and then two variables on the stack can end up having the same address, contradicting the previous hardcoded result. You get a different result of the same expression depending on order of optimizations. Clang won't remove this paradox, because there are programs and benchmarks that rely on both of these.
That may not be true in fact but it ends up that way because "undefined behaviour" tends to be implemented "to work" and we're used to behaviour of common CPUs that may have fed into expectation of what "to work" should be...
C does have rules that must be obeyed and string concepts of data types or lack thereof (like not having a string). There are assembly language designs specific to C++, until recently arm had java byte code specific feature set (Jazelle). C is dominant but I'd hesitate to say it is a machine code wrapper.
You have platforms with different [efficient] ways to pass arguments, operating systems and firmware with different conventions, and on top of that there are different compilers that will inevitably differ in fringe areas. All of this must be supported in a binary form.
What is the answer and why C’s answer is more problematic than any other? TFA reads like a misdirected blame or venting on “legacy”.
Might sound cynical, but: design your own hardware with a stable ISA. Develop an operating system on top of it. Ship your language on top of that OS and boast about the ABI stability to the few users of your language.
It could be argued that C is little more of a defined programming language any more than assembly languages is. It's worth remembering that C was invented in part, to abstract away architectural differences, so that Unix could be ported to new platforms.
If you want 'standard' with a sane well-defined ABI that never changes, there are better choices in 2024.
As the language grew and attempted to cover a larger set of architectures, "int", "short", "long" started to mean different things on different machines. C99 attempted to fix the mess with stdint.h.
You're taking the title too literally. The author is talking about how C is also a protocol, in that it is the defacto definition for ABIs.
You can write your program in any lang you want, thats up to you but using C as the os-level standard is problematic according to the author.
I don't care. That's my point. I missed the bit where the author allowed for that. That's why I described it as esoteric.
Nonetheless, that isn't the topic of the article, that quite clearly you haven't read.
C’s integers being wobbly-sized is the reason we have C compilers for pretty much every conceivable cpu architecture and we only have rust/swift/other compilers for a handful of carefully picked architecture.
There are entire industries where C-as-a-protocol is fine enough (and actually appreciated, even though the author doesn't like it).
There are entire industries that do not use X86 or ARM, let alone RISC-V. There are many use-cases for MIPS, weird/other architectures (PowerPC, 8051, m68k-derivatives, pic-micro)... I had a relative that used to work on control software for space stuff, the CPUs used there are even older and definitely not x86/x86-64, arm or risc-v).
So yeah, complaining that the C language does not fit your own very specific use case is very shortsighted.
We should all remember that the world does not revolve around us and around our own use case.
A while back there was a complaint that CAN communication should be explained as it's "too obscure". So much obscure tech is talked about on this site without explanation.
I think most of the developers on this site don't understand the wide world of embedded systems. C is a high-level language. It has a weird clunky elegance of it's own.
Once you leave the world of servers and natively built apps on an OS, things get really complicated. Just go on Digikey, Mouser or whatever is big now and see how many processers are out there.
I wonder if more platforms would consider having a stub that connects with the OS and talks to the rest of its kin via sockets of one sort or another. it does introduce a ton of lag and multiple kernel-userspace jumps I suppose.
>You don’t see England trying to improve itself, do you?
This felt unnecessary.
Regardless, the same is true of C, it can't change meaningfully because half the universe would break, so it's stuck with stability - stuck in time, warts and all.
But yes, only one round of "first past the post" is very simple, hard to go lower or commoner than that. :)
And it has a lot of added efficiency because there's no need to separately pick a figurehead - there's always one helpfully marked with a crown. (Of course the fact that changing the PM and the full cabinet can be done at any time without involving the electorate can help stability. But of course it also keeps inefficient coalitions going far too long, meaning eventually there will be a bigger correction event, which is bad for long-term stability.)
...wouldn't have mentioned it if I hadn't started reading and then remembered that I had already read it some time ago. Might have been https://news.ycombinator.com/item?id=33509223
can I write a c program and compile it on macos?
Yes, you can write and compile C programs on macOS using a compiler like `clang` or `gcc`.
I have both - I made a file hellodude.c what small program can I write there to test this?
#include <stdio.h>
int main() { printf("Hello, dude!\n"); return 0; }
I did that - how do I compile this?
clang -o hellodude hellodude.c
it works thank you
You're welcome!
Just suck it up and put the OS interface (with the appropriate paradigms and affordances for your language) into your language’s standard runtime. That’s part of the work of writing a language.
Whatever model you think should replace it will inevitably only be useful for some languages and as bad as a C interface (or worse) for others. The iAPX 432 was a notorious example — in hardware! Lispms, sadly, were another.
It is irksome that some will consider C the “lowest common denominator”. It ain’t. It’s just a shitty, path-dependent valley in the possibility space. And as for shitty, well, I use the toilet every day, and it’s not the high point of my day, but it’s where we ended up (so far) as part of the mechanism by which we power our bodies and also get to enjoy yummies. No point I’m complaining about it, it simply is what it is.
The OS interface is a huge and complex animal that provides crucial primitives for writing high-performing programs (async IO, memory management, timers, etc.). If you want your language to be practical for system-level programming, you need a 1:1 mapping between them.
Crafting a runtime that abstracts those calls and presents a new world to the programmer adds another layer of complexity and will probably worsen the performance.
But that’s what the article is calling for — claiming (correctly IMHO) that the C interface is pretty cruddy.
Unfortunately whichever higher level interface you choose will be inappropriate for most languages that aren’t the implementation language. I didn’t mention the 432 by accident: its baked in “object-oriented” architecture turned out not only not to help but instead slowed down the whole processor for no gain.
The only exception is when only one language is intended and there’s a tight coupling between silicon and that language (e.g. the lispm case).
... it's "the" programming language.
</smug>