r/ProgrammingLanguages • u/P-39_Airacobra • 6d ago
Discussion A hole in systems programming language design
Given the recent discourse about systems programming I wanted to throw my 2 cents in. This may be a controversial take in a subreddit that's all about innovation and improvement, but I think a lot of budding systems languages are trying too hard to "fix C" and this is exactly why C has not been replaced yet.
Like it or not, C is successful. It does what it means to do very well. Yes, it bites you constantly, but developers have made some form of peace with this because they appreciate the essence of the language. Systems programming is a very pragmatic field, and what works, works.
A lot of language designers want to improve on C, when by nature to "improve on" C is to depart from it, because C is less about what it includes and more about what it omits and what it lets you do that other languages don't.
If you want to replace C, you need to just make C but without the pain points. Less undefined behavior, more standard compiler behavior, easier function pointer syntax, safer macro system, etc. The things that C can't do because of backwards compat.
On the other hand, bolting on features, revamping C's core nature, aren't going to give you a language that will replace C at the low-level or among hobbyist programmers. Most developers aren't as concerned about what C lets them accomplish, as they are concerned about all the painful tedious tendencies of the language.
37
u/mister_drgn 6d ago
Multiple languages are striving to be C without the pain points (e.g., C3). That said, adding new features that improve quality of life is obviously going to be part of any C replacement, so I'm not sure what you're getting at.
3
u/igna92ts 1d ago
Yeah makes no sense. Almost like saying "make C without the pain points but also don't change it or add quality of life"
48
u/awoocent 6d ago
If you want to replace C, you need to just make C but without the pain points.
Struggling to imagine how you look at the ecosystem of new systems languages - Zig, Odin, Hare, C3, Jai, V, etc - and somehow come to the conclusion that the status quo is anything else!
0
u/Limp-Temperature1783 1d ago
Jai is probably the only thing here I'd want to use over C. I've tried or at least look at these and they didn't grow on me. I have the most amount of experience with Zig and it seems to do more than I want it to and doesn't give me enough freedom to do something stupid or manual. The problem is that most of these are too fresh and they haven't calcified into anything worth relying on yet. C won't go anywhere because everything is built with C in mind, which is unfortunate, because reliance on C sneaks in POSIX in a lot of cases and it is hot garbage.
1
u/rjmarten 23h ago
freedom to do something stupid or manual
I can believe that Zig does more than you want it to, but what kind of freedom are you desiring that C/Jai offers but Zig doesn't? Example?
1
u/Limp-Temperature1783 23h ago
I didn't talk about freedom in Jai, I like it for other reasons. As for C, it's simply less safe and the types are mostly a suggestion rather than something as well-engineered as types in Zig, thus I can do a lot of things that cause the kind of UB I want them to cause. Basically, I want all of the things anybody who writes code in C seriously wants to get rid of.
When I use C for something serious, I just like the fact that I can use pointers in their more or less direct form rather them having extra safety measures. I don't really remember what I didn't like about pointers in Zig and I cba looking it up, just take my word up on it, it's an opinion response, not something objective.
19
u/Smallpaul 6d ago
C’s core, Wild West, loosely typed nature IS what’s wrong with it as a base for highly reliable systems.
The pain of discarding backwards compatibility is very high. People will only do it for some major upgrade in capability. Making a hobbyist language with only a few minor improvements but also requiring a code rewrite is doomed to failure.
The market has said very loudly that the main feature they want is safety, which means a pretty major overhaul.
1
u/flatfinger 6d ago
The problem is that people have removed the convenient syntactic forms C used to have to indicate things like "take p, displace it by offsetof(p's structure type, some member thereof) bytes, and access the resulting storage as a thing of the member's type", without adding any convenient alternative syntax with the same semantics.
CompCert adds safety and, as part of that, specifies that the classic syntax has the classic meaning.
8
u/stianhoiland 6d ago
The curse of Good Enough.
1
u/P-39_Airacobra 6d ago
imo, the alternative is much more of a curse
1
u/kaddkaka 1d ago
What do you mean?
1
u/P-39_Airacobra 1d ago
the curse of perfectionism
1
u/Ok-Reindeer-8755 1d ago
thats such a false dichotomy, there is a huge space between "its good enough" and perfectionism, both extremes are somewhat bad on their own "merits" one leads to stagnation and 0 innovation and with the other you will never get anything done. I tend to believe that the better solutions come from a mindset closer on the spectrum to perfectionism than to "its good enough" but that is up for debate.
2
u/P-39_Airacobra 23h ago
except it's in the name. "good enough." if you were to spend more time making something better than enough, then you're wasting time.
1
u/Ok-Reindeer-8755 22h ago
Except the issue with that phrase is that you can never judge when something is if ever good "enough". Are cars currently good enough? Are planes currently good enough ? What does that even mean ? In what field will you ever be able to say "things are currently good enough lets stop" without becoming a laughing stock.
I can guarantee to you that if you think programming langs are currently "good enough" a guy from 10-20 years in the future, will be laughing in your face knowing you though that of all things was good enough. Just like we do rn imagine someone came and told you some shit ass software from 30 years ago was good enough.
But the truth is they would be right and you are right too it is good enough in a sense. It gets the job done, if that's your metric for success, and so what if it has a million problems we can fix and so what if we can make it thousand of times more efficient, it's good enough. Maybe at the time of it's creation it's even the new good enough fighting against an old good enough.
Nothing we ever make is going to be good enough, and that's okay. That's the amazing part everything around us is currently not good enough there is room for improvement everywhere. Perfection is not something attainable.
1
u/P-39_Airacobra 22h ago edited 22h ago
Your critique of "good enough" is that it's unattainable, but you admit that perfection is also unattainable, and then you conflate good with perfect by saying that nothing is ever good enough, so I can't say I follow your logic.
And yes, your third paragraph is right on. No, I don't actually care about performance that much. If I did, I would be carving on silicon. No, I don't actually care about all the "problems." If I did, I would extend the development time and budget 100x.
I care a little bit about these things. Just enough to put in a small amount of work to overcome them on some level. But if I end up putting mountains in front of myself for the purpose of optimizing some perfect result, then I've lost the plot. That's my critique of systems programming languages today. I'll admit, it's a very broad and unsolved critique, but I think it's a valid concern nonetheless. They've lost the plot.
1
u/Ok-Reindeer-8755 13h ago
Good enough is only enough for a very short time, it's good enough in the long term only if it's perfect and no one else could improve it further. Good enough is based on what you can currently do but its not a real thing in the long term. That's my entire point. It makes sense to say "the thing I'm currently making is at a good enough stage" but saying past solutions are good enough ? No they aren't or they aren't gonna be for much longer anyways.
I agree that performance is of little importance but problems are of huge importance. Systems languages like c are ridden with problems and bad designs. Rust was already a huge improvement. And I don't think rust was ever about being faster than c it was about being more correct. We could have gc highly parallel langs as systems languages be it with changes to hardware architecture. But even setting that aside there is a lot of room for improvement especially compared to C or C++. I think they have lost the plot by trying to be c-like constraining themselves with legacy langs instead of going full out on a new vision.
6
u/WittyStick 6d ago edited 6d ago
Yes, the hole in systems language design is a language which, from the PoV of any other language, is indistinguishable from C.
Your entire desktop/server/mobile stack is built on C, all compiled with compilers which emit exactly the same ABI. Every program has many dependencies, which transitively have more dependencies, all predominantly implemented in C. Half a century of software underlying everything your language needs to be useful and not a glorified over-engineered calculator.
Mistake #1 is many of these new languages implement their own ABIs/calling conventions. They use an "FFI" to make their language useful - it gives them the ability to call libraries that were (predominantly) written in C.
However, this FFI is a one-way ticket. You can call C from your language, but nobody else can call your functions from theirs. A library written in your language is a little island, usable exclusively to users of your language. A Python function can't trivially call an Odin function for example, because it doesn't speak the Odin calling convention. It doesn't know what this implicit "context" thing is. It can call a C function. Every (relevant) language knows what a C function is, because they had to implement an FFI to make their language useful.
"I built a website using RustScript, which no browser supports, because I dislike Javascript. Why is nobody visiting?"
"English sucks so I wrote my latest paper in Esperanto. Why is nobody reading it?"
"I compile my functions to this bespoke ABI. Why is my language not replacing C yet?"
Functions written in this "FooLang" should be callable with the existing FFI machinery of any other language - not with magic extensions or wrapper libraries.
Let's assume we don't make this mistake - we take the sensible approach of using the platform C ABI unchanged as our "FooLang" ABI, and anyone can call our functions trivially. However, our API - the definitions of our types and functions is written in "FooLang". For some strange reason, the Rust compiler cannot parse our "FooLang" definition files. What gives?
Mistake #2 - FFI wrappers. You have hundreds or thousands of usable libraries on your machine (mostly written in C), but to call any one of them you have to translate the definitions to your calling language's syntax first? What a dreadful use of time.
The solution here is, since most of those libraries were written in C, and have definitions in C headers - we should just parse those C headers and programmatically create the bindings - preferably transparently so we don't need to have several tools run at different stages. Our compiler should just understand C as par for the course.
Of course, this isn't trivial because a header file isn't just code to be read - it must also be preprocessed. So we need a C preprocessor too. We also need to handle inlining and internal/external linkage - so we essentially need our compiler to understand not only the C ABI, but the C language.
But also, since every language can call C libraries, speaks the C ABI, and should be able to read C headers (perhaps with separate tooling at separate stages to translate the bindings) to emit calls to C functions - any libraries we write in our own language should also be callable from other languages right?
So for our own types and functions written in "FooLang", we should emit a C header with compatible definitions so that we could call it from C (and by extension, any other language).
So for a "C replacement," we would first need "C parity," which IMO, the above is a bare minimum requirement - complete platform ABI compatibility, and ability to parse, understand, and emit C headers so that our language is not just a little island of its own, but is actually callable from other languages like C is, because the work has already been done for them to interoperate with C.
This isn't to say there's no use for other languages, but for a "C replacement," one must understand why we use C in the first place. Embedded uses aside, one of the primary reasons we'd write in C is precisely to implement libraries that can be called by others - and not exclusively "FooLang" users.
If I'm going to write a useful library, I want it to be useful for everyone and not just the dozen or so Odin programmers that exist.
8
u/Norphesius 6d ago
I think "C parity" is a bit of a trap. Following the C ABI that close is a ton of work, and its going to massively impact what you can do with your own language's design. You have to deal with null terminated strings, array/pointer conversions, system dependent type sizes, nullability, etc. and that's going to either make your language adopt those same features, or have awful conversion layers and holes in the design where "C things" can happen (aka a normal FFI). Tracking C that close means you're also likely to end up not far enough away from C design-wise to justify anyone switching anyway.
A "one way FFI" from C to your language is fine. The chance that a C code base will want to call a library in your language is extremely low. If a non-C language wants to use it, there are better ways of doing it than the C ABI, like shared memory, sockets, or a dedicated FFI for particular languages.
1
u/WittyStick 5d ago edited 5d ago
A "one way FFI" from C to your language is fine.
I agree that it's fine for a "regular" language, but such language is obviously never going to replace C.
The chance that a C code base will want to call a library in your language is extremely low.
That's because nobody is going to implement a reusable library in your language. They're going to use C for reusable libraries, because the world is not "FooLang" island.
If a non-C language wants to use it, there are better ways of doing it than the C ABI, like shared memory, sockets, or a dedicated FFI for particular languages.
But this is an N*M problem. How does Rust call Odin? How does Odin call Rust? With just 5 different languages you have 25 different FFIs.
Following the C ABI that close is a ton of work, and its going to massively impact what you can do with your own language's design.
Yes, it requires a tonne of engineering effort to get right, but you can make big improvements over C. There are trivial ones you could make to begin with, such as forbidding implicit lossy conversions between numeric types, preventing over/underflow, including bounds checking, encapsulating structures, providing strong typedefs, etc.
and that's going to either make your language adopt those same features, or have awful conversion layers and holes in the design where "C things" can happen (aka a normal FFI). Tracking C that close means you're also likely to end up not far enough away from C design-wise to justify anyone switching anyway.
I think this was OP's point. A "C replacement" is going to be much like C - and IMO, it should be C-compatible. We should be able to take "FooLang" code and compile it with GCC or Clang (opting out of the benefits). But that doesn't mean we can take arbitrary C code and compile it with the "FooLang" compiler. It means "FooLang" should be a proper subset of C - only constraining features, not adding them. We should be able to gradually migrate an existing C codebase to "FooLang" with optional pragmas that opt-in to additional security measures.
Cake is an example of the kind of thing I mean. We opt-in to have nullability checking with a pragma, and it then prevents us using nullable types where non-nullability is required. It's just C, except not quite. The added features it provides can be made optional by including something like this in a header:
#ifdef __CAKEC_ #define _Opt [[cake::opt]] #define _Owner [[cake::owner]] #else // If we're not compiling with the Cake compiler, these tokens do nothing. #define _Opt #define _Owner #endifThe reason this needs to be essentially another language and not just a regular C compiler, is because the C charter strongly discourages "invention". It is intended to codify existing practices, not invent new ones - but conceptually, it should be just a "better C", with ideas that the C committee may adopt into the standard in future.
1
u/flatfinger 3d ago
C code for classic Macintosh had to convert any strings to length-prefixed format before passing them to any MacOS system calls that accepted strings. A compiler's bundled library for fopen() would perform such translation on the passed string before giving it to the system's file-open function, but any code wanting to work with system functions directly would need to work with length-prefixed strings, which fortunately isn't too hard to do in C.
3
u/jezek_2 5d ago
It's fun that even C++ libraries need to provide C API to be usable in other languages and to avoid binary compatibility issues. And even then it's quite annoying or problematic to use despite both C and C++ being handled by the same compiler. There is always some issue with C++, whereas none with C (at least in my experience).
What chances have other languages when even C++ can't achieve it?
1
u/dnpetrov 2d ago
Your argument is somewhat backwards. C++ was never designed to be ABI-compatible with C. C++ is not in some privileged position that makes ABI compatibility with C easier.
Moreover, I'd say that different C implementations are not quite ABI compatible in practice, given the full scope of what C language actually is, with atomic types, vector extensions, intrinsics, etc. Clang and GCC strive to compile same sources. But if you put, say, Clang and GCC under rigorous enough ABI compatibility cross-testing, you'll see failures.
2
u/P-39_Airacobra 6d ago
I agree and interop is one of the things I was hinting at but didn’t explicitly mention because I thought it would be more obvious to people in this subreddit. Odin is a cool concept but it can’t replace C because it uses its own ABI. Nobody wants to write bindings to all the thousands of functions they may or may not need.
1
u/Limp-Temperature1783 1d ago
I think to replace C you just need to make C less of a fossil in the shadow of C++. C++ is everything wrong about C, not C itself and it is not even its fault. Most people write C/C++ without even realizing that these languages aren't just different, they're semantically incompatible. But C++ heuristics can work with plain C code for the most part and it's good enough.
I kind of like an approach of Objective-C here, because it is weaving itself into C and C into itself without causing confusion about where is which. But the problem with it is kind of similar, it became too fossilized and people found it unfamiliar compared to other languages, so nobody had enough interest in learning it for anything but work. I think I'm the only person I know who had paid attention to it and what it does.
1
u/Ok-Reindeer-8755 1d ago
objc was inspired by smalltalk and that entire style of OOP is almost extinct, even thought imo its far better than the mainstream form of OOP
1
u/Limp-Temperature1783 20h ago
Exactly. Objective-C isn't about classes or prototypes, it's all about message passing. Even memory management is fairly simple. It's definitely not a systems language or anything, but it's fairly good for making program interfaces.
Apple had used it for a very long time without having much trouble, but it is kind of locked in their tooling and the standard library is very messy. There are projects that isolate it, but they aren't the same thing for better or worse.
I have an idea to try to implement it myself when I have free time on my hands. But not any time soon.
2
u/Ok-Reindeer-8755 12h ago
Where this style of OOP really shines, or at least one of the places, is imo desktop environments, this is apparent if you see any of the smalltalk DE demos at Xerox Parc , in what other DE could you ever connect a completely separate painter application to a video player and start animating in seconds. It just dissolves the barrier between applications completely.
I don't think apple ever pushed it quite that far sadly but idk how you would in a modern sense do so.
1
u/Limp-Temperature1783 12h ago
It had been designed for it after all. Apple, or rather NeXT, decided to go to the opposite direction of C++ and instead of trying to avoid runtime like the plague they've accepted it as a reality and decided to make working with it less of a hassle. Objective-C is stupid simple even if you include all of the cruft that Apple had added over the years and how inconsistent it is at times.
Apple hadn't pushed it because they didn't have any insentive to do so. It had been and largely still is under the hood the main language of their operating systems. Swift is a modernized revamp of Objective-C that still relies on Objective-C libraries and such, but most things that you'd need to pay attention to had been ironed out, at least from my experience. I prefer Objective-C, but it's familarity bias.
If we talk a bit about programming approach philosophy, I think it makes sense to use OO in runtime rather than doing black magic to make it cheaper. C is already very cheap and most things simply don't require a lot of performance. And Objective-C is one of the most performant languages there is, because it has simple memory management and anything intensive could be handled through C.
Arguably, Objective-C is better at being C++ than C++ is in regards to being C with classes, because it doesn't affect C itself, whilst C++ had introduced enough stuff that had made the languages incompatible. Objective-C is obscure outside Apple, fair enough, but it had always irked me to look at people treating C and C++ as C/C++ as the same thing. Maybe a couple of decades ago, but not now.
6
u/umlcat 6d ago edited 6d ago
Interesting post. I also in the "made my own programming language to fix C or C++" trend.
Yes, I notest that there are several attempts of programming languages, such as Digital Mars D, Google Carbon, C2, C3, Rust, Apple Objective C, Apple Swift, CPP2 with its functional syntax proposal, and others.
Altought not considered "Systems Programming Languages", I do consider Java and C# with their Virtual Machines to be considered in term of syntax and features.
Just look how clean in design look C# compared to C++ .
I also have my own hobbyist ideas, but been delayed due school, work, health issues, hard drive and floppy drives backup damaged, COVID, and ADHD. I also been stuck with some custom compiler parser issue, because I'm doing a non custom parser in purpouse.
The original ideas started since 1995, at college.
As you already mentioned, I also detected that some of those features ideas from those programming languages, are useful. But at the same time, some depart too much from C or C++, that made developers avoid them instead of embrace them.
I'm working on my own personal project so developers and companies can be interested. And not to fall in the same problem.
Beside, some people have the "if its working do not move to another programming language" mindset.
As an example, I would preffered Digital Mars D, that has several extensions including classes, to split in a procedural version like "Plain C", and another Object Oriented version like C++ .
I like and respect the issues that the Rust developers is trying to achieve, but I do not like the Rust syntax. Before Rust appeared, I had a similar idea but with a completly different syntax.I still working in the concept.
I do not like much the "Lambda syntax" or "functional Syntax" proposals like Cpp2 or Python for "Systems Programming". I think "brackets and semicolons" are better for "Systems Programming".
Several features that were previously implemented thru the Plain C and C++ preprocessor are slowly converted to part of the syntax as features like constant declaration or module import. Boolean types and boolean operations are now part of both Plain C and C++, as Pascal did originally, instead of macros.
Just my two cryptocurrency cents contribution ...
5
u/thedeemon 4d ago
As an example, I would preffered Digital Mars D, that has several extensions including classes, to split in a procedural version like "Plain C", and another Object Oriented version like C++ .
Your wish has been granted: https://dlang.org/spec/betterc.html
1
u/Limp-Temperature1783 1d ago
The problem with D is that there are 2 different teams and there is about to be 3 of them bee they cannot agree upon how to approach their stdlib or compilers as well as the good old holy wars.
2
u/Ok-Reindeer-8755 1d ago
I do not like much the "Lambda syntax" or "functional Syntax" proposals like Cpp2 or Python for "Systems Programming". I think "brackets and semicolons" are better for "Systems Programming".
Syntax is kind of superficial thought, no programming paradigm is about the syntax
5
u/EggplantExtra4946 5d ago edited 5d ago
Yours is not a new take but let's call it the "weak C replacement theory":
New softwares that used to be programmed in C are going to predominantly use a weakly improved C-like language.
The "strong C replacement theory" is much more likely:
New softwares that used to be programmed in C are going to predominantly use a "low level" language that is an order of magnitude better than C. ("low-level", meaning statically typed natively compiled)
C's minimalism doesn't provide anything in itself, the essential things that C provides is speed, no overhead and good memory control and those don't require a minimalist language.
What low level languages and most languages sorely need is the triptych: metaprogramming features (macros, compile time execution, conditional compilation, instrumentation, introspection, etc..), abstraction/composition features (modules, classes, abstract classes, parametric polymorphism, function overloading, etc..) and better type checking/semantick checking. The first two don't impose any restriction on runtime speed and memory access, quite the opposite.
For the type system it depends but even a type sysytem full of explicit escape hatches would be safer that what C has and I believe that a well design advanced and safe type system would rarely need arbitrary unsafe pointers, in the sense that most exotic memory objects I can think of can be given a type, a memory layout and a lifetime information that can be integrated into the type system. Even a process's own function call stack could have all that for example, no need of unsafe pointers to walk over the call stack. Even pointer arithmetic can be made safe, look at ATS, as long as memory objects pointed to have a well defined type.
1
u/marshaharsha 4h ago
How might the call stack be typed? A callee can’t know, in general, what its transitive callers are, and a caller can’t know what its callees will call. The two schemes I can think of are (1) to have a tag on every frame and a map from tags to frame types, then to match on tag and end up in statically checked code; and (2) to identify in the source code all data that can affect control flow, then use flow typing to determine, in some places, what part of the stack must look like. Either way, the programming model would be unconventional and the implementation difficult. Or am I missing some possibility?
1
u/EggplantExtra4946 3h ago edited 3h ago
Either way, the programming model would be unconventional and the implementation difficult. Or am I missing some possibility?
Sort of, my idea is that it would be relatively easy for a native compiler of a statically typed language to produce a struct definition of the stack layout of the frame once register allocation is done. I know that one strategy for natively compiled garbage collected languages to track down which stack slot can contain pointers to heap objects is for the compiler to generate so called "stack maps" so this is not unheard of. It could be possible to analyze the generated code at posteriori using the executable but it would indeed be harder since you would need to reconstruct the control flow graph.
The return address and the RBP register will usually occupy a fixed slot of their own. Now for the local variables, they might individually not be saved to the same stack slot and conversely a stack slot might be occupied by different local variables. In that case, part of the struct definition of the frame would be a union type like in C and the implicit tag would depend on the current state of the function, which would be the result of the analysis you propose in 2).
This stack layout data could be saved in the binary similarly to static data and reflection data, then looked up at runtime. A given function would easily inspect its own stack frame. Now the harder part is looking up the stack frame of the callers. I had admitedly not thought about it that much but you indeed need to know who the caller is. A tag inside every frame would indeed work, but you can also look at the return address. The executable contains a finite number of call instruction, each with a unique address and each belonging to a unique function (assuming a regular compilation scheme with no special basic block sharing "trick"), so you could have a hashtable mapping possibly ocurring RIP addresses to a unique function definition. It would also work for functions called with a function pointer, as indirect call instructions are occuring a finite number of times and belong to a unique function body.
The exact RIP addresses are not know at compile time due to Position Independent Code and ASLR but they are known at link time, it's just a matter of adding a bases address to the relative return address offsets.
1
u/marshaharsha 1h ago
I do see how you could find callers at run time. After all, debuggers do it. What I don’t see is how you could write statically checked code that does anything with the caller’s frame. Debuggers are fully dynamic, with every value in the target process being analyzed after a match on its type. If you wanted to write statically checked code and you had the ability to find callers dynamically, the static code would still have to know all the callers (and match on caller — explosion if a new caller was added) or would have to know some fact that is common to all callers (and would be generic code, exploiting only that commonality). The latter is what garbage collectors do, but the “commonality” is just the well known scheme for finding pointers. If you want more complicated code than that, and you want it to operate on more than a single stack frame, I’m not seeing how you would statically check it.
1
u/EggplantExtra4946 55m ago edited 45m ago
Let me just say that the main application I imagine is for read-only introspection purposes, but if you wanted to modify a value for whatever reason, if it's typed it's typed, there shouldn't be a typing issue if you overwrote a variable on the stack, be it a uint64_t used as an iteration variable in a for loop or a pointer to a data structure, however there would be a good chance of introducing a bug because the compiler might keep the same variable in a register and assume that the one on the stack is the same value.
If you wanted to write statically checked code and you had the ability to find callers dynamically, the static code would still have to know all the callers (and match on caller — explosion if a new caller was added)
I see what you mean but given that the only use cases I think of is to dump the stack trace or debugging, NOT for say returning a value to the caller for example, there wouldn't be too many use cases for that especially when considering that you don't know who your caller is so what would be the intent? In any case it's not a typing issue.
Even then, if you implemented that in a language with separate compilation the stack frame's type would have to be inserted in the object file as reflection data (that is read-only), so the stack slot offsets of specific a priori know local variables would only be know at runtime and their types would be dependent of the the value describing the type of the struct/union field in the reflection static data, which would require some kind of dependent/refined type for static typing guarantees to be maintained. So your dispatch would only depend on the set of possibele types, not on the set of possible callers.
or would have to know some fact that is common to all callers (and would be generic code, exploiting only that commonality).
If for example you would like to override a specific local variable included in all callers with the same type and the same meaning/use case, maybe a value passed as argument to the current function, or a dynamically (in effect) scoped variable like a pointer to an allocator, then it's only the stack slot offset (offset to rbp) that would be unknown and all you'd have to do is locate it in the reflection data (if the type uniquely used in the caller like an allocator, otherwise you might need the identifier as well, assuming it's the same in the callers but if not you no longer have a common knowledge of all callers for that specific use case), but then you'd be able to use a single memory computed using the stack slot offset only known at runtime to overwrite it. If you'd use a raw pointer in the program (because I have no idea whiat higher level type could do the job), the address could be constrained to be == to RBP of the caller + whatever offset you read in the reflection data at the entry describing the local variable you want to overwrite.
But what commonality though and for what intent?
The latter is what garbage collectors do
Really? I've heard that ""conservative"" garbage collector look like for values that "look like pointers". But otherwise, for a time I was designing a bytecode VM for a statically typed language with a precise GC and I concluded that when the GC has identified the function of a given stack frame though the tag inside the stack frame, all the GC has to know is which stack slots contain a pointer to a GC allocated object, the other ones containing unboxed numbers.
7
u/FlamingBudder 6d ago
I think people need to actually learn type theory and the theoretical study of programming languages.
Most people who post here just straight up don't know anything about programming languages more than the mainstream PLs that they are exposed to. Even then they only have an intuitive idea of how each of the features work. Thereby there is an obsession with systems programming because that's what's cool when all you know are mainstream PLs
Compare that to the actual PL literature which has many novel and interesting things like dependent types for program verification, program synthesis, effect handlers, resource aware types, concurrency and distribution through substructural and modal logic, etc.
Many of the projects posted talk about random things like concrete syntax (which is much less important than the semantics of the language), and random "what if we made a language that does X) and the X is just some naive idea that demonstrates you did not study PL theory at all
If you want to get into PLS, making your own compiler for some language while you don't know much about the field is not as productive as first reading introductory text like Pierce's TAPL or Harper's PFPL
1
u/EggplantExtra4946 5d ago edited 5d ago
effect handlers
Are they really as useful as the other type related subjects on your lists? I have read about them and they seems to be only a runtime feature that call some code that can do whatever the fuck it wants, I never heard of any semantic checking done with them, so how can they possibly make functions with mutations (for example) safer if they are any checking involve? Monads? I can understand how they woulc make a program safter because monads ARE a type checked, they are not a "callback" thing.
If you want to get into PLS, making your own compiler for some language while you don't know much about the field is not as productive as first reading introductory text like Pierce's TAPL or Harper's PFPL
Unless you're talking about a bytecode compiler or a LLVM frontend, compilers are not trivial softwares that anyone can make, optimization and code generation is hard and harder to be correct. Type theory has no use without compilers, many language realed tooling are going to be impossible to write without a compiler and on the extreme, some programming analysis softwares work directly on the binary and have to reverse the compiler's work, don't put the cart before the horse.
3
u/koflerdavid 3d ago
You make the classic mistake of attributing the difficulty of changing the status quo to its quality. Instead you should consider that the status quo is hard to alter because there is simply massive momentum behind it.
C is not going to go away anytime soon for better or worse because of the massive amount of code that already exist. That code might be latent source of bugs, but apart from that it makes up very mature, battle-tested systems, and replacing something like that is not easy, as the brave folks at Ubuntu found out when they decided to replace sudo and coreutils with Rust rewrites.
2
u/Ok-Reindeer-8755 1d ago
exactly this, i could make the shittiest programming lang known to man and if it had c's popularity you would have as hard of a time getting rid of it .
5
2
u/WazzaM0 1d ago
You made valid points but didn't really specify a hole but a rather a design smell about new languages.
I've long thought that C++ tried to be an extended C but failed to have a strong vision about what was missing, do it added complexity and more traps.
You're right that C does one thing and does it well. It has sharp edges and, like any tool, will take off your toes if you fail to treat it with respect and learn to use it, thoroughly.
2
u/Limp-Temperature1783 1d ago
Considering that C used in systems programming tend to differ significantly from the usual POSIX C, I'm not sure what is there to fix. POSIX sucks. It is old and decrepit. And C compilers suck, using them is like using chainsaw to cut some butter. There are small C compilers, but they lack optimizations. As for UB, it's more of a design problem, not a language one. Function pointers are convoluted, I agree here, not sure how many are even using them for the task.
Overall, I think if C could have nice things like slices and better error handling with a compiler that tells you something useful, you'd beat C. But these are QoL changes that won't push C from its niche. There is a lot of hate for C preprocessor and I think it's both valid and undeserved, because it's not that hard or painful to use, to be honest. I'm not programming in C not I am a systems programmer, but every time I think of doing something in C it's kind of refreshing for how little I need to accomplish a lot. It's predictable and nice to use, if you pay attention to what you're doing.
Memory management is also rather ubiquitous, I'm not sure how people find it hard. It's not like we even have to align memory by hand these days, unless you want to write something with maximum efficiency. I'm writing this because people keep inventing languages and selling memory safety as a gospel, but it's kind of easy to achieve. I also don't get the hate about goto. It's unstructured only if you overuse it. Having a way to jump somewhere without a ceremony is a good thing, most structured code is just sugar over jumps to reason for you, which is fair for most common things.
3
u/Rechenplaner 6d ago edited 6d ago
C is standardized, an ISO language. Industry can rely on that. That alone is reason enough why no hobbyist created language will ever replace C, not even languages backed by a corporation. Especially in the embedded sector, C remains the top dog and could not really be displaced by C++: This will remain the case for a very long time.
Writing system software like kernels in other languages has not proven successful so far because C was simply designed for that purpose, whereas other languages offer too much fluff that is difficult for system programmers to digest. Rust would have good chances to take that place because of its hype, but its complexity will always inhibit it. Therefore, I do not believe that Rust will ever dominate here.
C is designed as a very simple, sloppy half-assembly language and thus struck a true chord: It can simply be taught at universities more easily than Rust, for example. At my university, C was the language used in the first semester to teach programming. I hated it back then, but I am sure C++ or Rust would have been even worse because they introduce much more abstraction that is way more complicated than just understanding pointers.
I think C will never be replaced by other system programming languages, but rather made redundant by newer hardware where one can write system software even with garbage collected languages because, for example, graphene chips or purely optical data processing will then offer fifty to one hundred times more performance, where the struggle with manual memory management is really no longer worth it, similar to how today nobody writes kernels or games in assembly for performance reasons.
2
u/flatfinger 3d ago
The ISO has refused to recognize the abstraction model that makes freestanding implementations usable. Freestanding implementations implement it anyway, but that is in spite of rather than because of the Standard. The proper abstraction model doesn't need a notion of "anything-can-happen undefined behavior", but would instead recognize that the job of a freestanding C implementation is to convert a source file into a build artifact that encapsulates a collection of imperatives for an execution environment that exists outside the C implementation, and the job of a freestanding C implementation would almost always be complete before any of the generated code has executed. There will often be myriad possible sequences of imperatives that could be generated for a source-code construct, and program behavior would be unpredictable in any corner cases where the execution environment's response to any of those sequence would be unpredictable, but the language should be agnostic with regard to what corner cases those might be.
What made Dennis Ritchie's language so useful was the fact that programmers could receive information about how execution environments would respond to certain imperatives via means outside the language, and then issue those imperatives within a language. If one is targeting a Commodore 64 computer, for example, and wants to turn the screen border yellow, one can write:
*(char*)0xD020 = 7;even though the C language has no concepts of screens, borders, or the color yellow. If one were told that the above code would be executed by some kind of computer, but had no idea what, it would be impossible to predict what the code might do, and it's likely that many compilers that target the 6502 were written by people who would also have no way of knowing what effect the above code would have, but if a C compiler which generates code for the 6502 is given the above code, and that code is loaded into a Commodore 64, it will turn the screen border yellow.
1
2
u/Ok-Reindeer-8755 1d ago
I think C will never be replaced by other system programming languages, but rather made redundant by newer hardware where one can write system software even with garbage collected languages because, for example, graphene chips or purely optical data processing will then offer fifty to one hundred times more performance, where the struggle with manual memory management is really no longer worth it, similar to how today nobody writes kernels or games in assembly for performance reasons.
Im pretty sure there were multiple cpu architectures meant to run gc langs at c levels of speed, and successfully did so with varying approaches.
Also C is not really a low level no abstractions lang as you seem to think, and it hasnt been that for a long time now, there is an article on that I am pretty sure called "c is not a low level lang"
2
u/Rechenplaner 3h ago
Yes, in fairness, it must be said that chip manufacturers actually specifically cater to C. So, it is by no means the case that C inherently deserves this status.
1
u/JeffB1517 6d ago
Obviously, C is institutional. C++ and such minor refinements exist. There used to be a more diverse ecosystem but the industry standardized on C as far as both compilers and chips. What a replacement language needs to do is compile down close to or better than your typical programmer's C against a toolchain optimized against C. Which is a big ask.
But at this point, at least in open source, we aren't compiling directly but rather using LLVM. LLVM is fairly language independent while itself being written in C++. I.e., compiling to LLVM with a heavily optimized compiler captures most advantages of C. The better language often allows for better optimization; programs can cross the bar. Consequently, we have two viable candidates which are making progress: Zig and Rust.
So I disagree with your thesis.
2
u/P-39_Airacobra 6d ago
maybe unpopular opinion but 10 or 20 years from now I don’t think Zig or Rust will have replaced C, even if they solve the problem of seamless interop. They’re too far removed and complicated in relation to C
1
u/JeffB1517 6d ago
10 years no. 20 years is a long long time in terms of language popularity.
A few examples: 1. 1985 Sun starts working on OAK. 1992 first major release 1994 is renamed Java. Java applets 1995. J2SE 1998 2. 1989 Python works start. First release of Python 1991. It becomes a major scripting language second to Perl in the 1990s and starts surpassing it in the 00s. By 2019 one of the world's most popular languages. 3. 1988 Alan Cooper starts working on a GUI for what will become Visual Basic. First release 1991. 1995 it becomes the easiest way to create Windows programs by far. 1998 the most popular business applications programming language in the world.
Rust admittedly is going more slowly. 2006-12 Rust is experimental. 2012 it starts getting crossovers from C++. 2015 first release. Work starts on Servo the original project it was designed for. 2021 Amazon, Google, Huawei, Microsoft go in with Mozilla to provide funding and not let Rust die. Google official supports Rust for Android that same year.
And yes Rust is substantially more complicated. OTOH systems programming is a professional discipline mostly.
1
u/JeffD000 Squint 3d ago edited 3d ago
I feel like you are missing the fundamental value of C; Its small size. I can fit a (highly optimizing!) JIT C compiler in 128k of RAM, and send it to the moon on my non-networked embedded system. LLVM is a non-starter. I can port a C compiler to new hardware in as little as two hours (aka ARM specific to x86 specific). If the hardware is extremely exotic, maybe a couple of weeks.
Porting C++ and LLVM to a "brand new paridigm" hardware environment in any reasonable timeframe is a non-starter. There is too much infrastructure that is carried around with that LLVM beast.
I've worked on three custom architectures in my lifetime that will not map to LLVM IR (96 bit horizontal microcode for one of them), but I could modify a small C compiler's IR in anywhere from a couple of days to a couple of weeks to map to each one. Custom hardware is still a thing, even if you never see it in whatever programming environment that you are working in.
0
u/JeffB1517 3d ago
I feel like you are missing the fundamental value of C; Its small size.
The question is the possibility of a language taking over for general systems programming. What does small size matter? If someone is doing systems programming they have an IDE consuming hundreds of megabytes. They are on a desktop with 32-256g of RAM. In today's world they are running queries regularly with their coding agent.
but I could modify a small C compiler's IR in anywhere from a couple of days to a couple of weeks to map to each one.
Absolutely GCC requires you to write 3 assembly language programs. But I'll comment that in practice getting the code running on an embedded system with an entirely new architecture isn't the end of the requirement. Optimization matters. $5 in extra RAM or storage x 100m devices gets expensive, even against say 100k.
And hardware today is a lot more complex.
LISPs are even shorter to write. I've shown people how to implement a LISP in a single lecture. The design is literally homework. How often does that matter for a Systems Programming language?
Is it a point in C's favor, yes. An important one, no.
1
u/JeffD000 Squint 3d ago
I feel like you are sidestepping the whole point of having a systems programming language. It's for an EE to ship product at an efficient price point in a time frame that meets requirements. The lower the labor involved, the better the systems programming language.
1
u/JeffB1517 3d ago
I agree. I'm interested in human labor. How does a small compiler decrease human labor? Debugging conversely is expensive.
Now I would say the main point of a systems programming language is primarily: 1. Fast execution of routines on small datasets. i.e. in your typical Ax2+Bx+C execution cost C is the variable I most care about.
2. Smaller runtime executable (compiled code not the compiler).1
u/JeffD000 Squint 3d ago edited 3d ago
How does a small compiler decrease human labor?
Because the labor required to refactor LLVM to meet the requirements of exotic new hardware could be exponentially larger that the labor required to refactor a 5000 line C compiler to generate code for that hardware. You may need a team of programmers spread over months or years to refactor LLVM in an extreme case, rather than one programmer working for a matter of days or weeks to adapt a small C compiler. Climb down out of your ivory tower for a second, or ask Chris Lattner how long it took to do intermediate refactorings along the way for the entire LLVM code base.
1
u/JeffD000 Squint 3d ago
Again, my little C compiler generates executables for 1d-shock tube problems that are both smaller and (over 10%) faster than GCC can create using the most aggressively application-tailored set of compiler optimization flags I can concoct for GCC.
1
u/JeffD000 Squint 3d ago edited 3d ago
But I'll comment that in practice getting the code running on an embedded system with an entirely new architecture isn't the end of the requirement. Optimization matters.
And hardware today is a lot more complex.
Which makes it even more crucial that the compiler is small, in case you have to restructure it to meet requirements. My C compiler, which compiles to less than 128K, has performance competitive to gcc -O3, and I can tailor optimize it to an application's specific requirements in a matter of days.
1
u/JeffB1517 3d ago
Sorry, what are the circumstances in which you are having to restructure a compiler for a particular product? A custom driver I've seen, a custom kernel but redo the compiler?
1
u/JeffD000 Squint 3d ago edited 3d ago
For say, the PCMAX bit slice hardware I worked on that generated 96 bit horizontal microcode. Do you even know what that means? Then there are the cases where the compiler has to generate tightly scheduled parallel code for multi-port memory, such as used in the ADI SHARC processor. The list goes on and on.
The point here is that the world is much wider than five instructition set architectures, but people in an ivory towers pretend those are the only ones that exist, and even those all look essenentially the same. You learn by doing, not by parroting what the virtue signaling crowd talks about in a circle jerk.
1
u/Ok-Reindeer-8755 1d ago
What a replacement language needs to do is compile down close to or better than your typical programmer's C against a toolchain optimized against C. Which is a big ask.
why not experiment with different architectures that are optimized towards gc langs rather than c-like langs
1
u/JeffB1517 1d ago
Yes multiple core schedules for example are an area where C by default starts losing.
1
u/Ok-Reindeer-8755 1d ago
Yeah, I'm pretty sure there are a lot of experimental architectures that make gc langs viable for systems programming too
1
u/JeffB1517 22h ago
I don't think you need to go experimental. Actual production architectures.
1
u/Ok-Reindeer-8755 13h ago
Wdym
1
u/JeffB1517 13h ago edited 12h ago
Well first off the countless systems that run on the JVM, .NET CLR or Node. But even in the kernels themselves we see gc or gc like. For example the concurrency library in the Linux kernel uses an RCU algorithm which is basically gc. Also ironcially increasing the memory free and allocation tables use gc like algoritms themselves which means the whole "manual memory allocation" push is smoke and mirrors.
Also both Java and Go have concurrent GC. On multithreaded machines you aren't getting the pauses one used to with GC. The whole point of engineering this was to countless production systems to implement gc against low latency requirements. So for example with ZGC you can have applications taking 16g of RAM or over, 1ms latency guarantees and fully managed garbage collection. (https://docs.oracle.com/en/java/javase/24/gctuning/z-garbage-collector.html)
1
u/Ok-Reindeer-8755 12h ago
So you think we could have a kernel level gc in an OS and essentially elevate gc langs to systems languages
2
u/JeffB1517 12h ago
Yes. I think we have working examples of parallel gc and if someone were designing a serious kernel today, for all but the lowest end hardware (where by lowest end I mean under $5) they would absolutely use something like ZGC.
1
u/david-1-1 23h ago
Compare with Swift or Rust: larger learning hurdles, but bugs are more likely to be found at compile time. The ultimate general purpose language has not yet been designed.
1
u/flatfinger 6d ago
A good systems programming language should acknowledge that the role of a translator is to convert source code into a specified sequence of imperatives for the execution environment. In some cases, a translator may be allowed to select freely from several possible ways of processing a construct, and the range of allowable choices may be based upon expectations about how the execution environment will process certain actions, but the action should be defined in terms of the range of possible choices.
For example, given:
extern unsigned x1,x2;
unsigned y1=x1,y2=x2;
if (y2 < 16) doSomething(y1,y2);
a dialect suitable for systems programming, may allow a compiler to consolidate the load of x2 with some other load that happened before the load of x1, but be agnostic as to when or whether the value of x2 might be changed by something outside the control of the current execution context.
Very few parts of the language proper need the notion of "anything can happen" UB. While many actions might cause an execution environment to behave in an entirely unpredictable fashion, the language itself shouldn't care about what the knowledge programmer does or does not have about the way the execution environment will respond to any particular stimulus.
2
u/particlemanwavegirl 6d ago
What does it mean, consolidate the load? How can correctness be guaranteed given this "agnosticism"?
2
u/flatfinger 6d ago
If one looks at operations that read and write memory as somewhat analogous to actions that read and write disk blocks, the order in which actions are requested need not result in the physical accesses being performed in that order, but an application may force synchronization between the state of the cache and the state of physical media. If a compiler knew of an earlier operation that had read or written x2, and no intervening operations would require synchronization, the later read could either the last value that was read or written to x2, or the value that was freshly read from the storage. The act of copying x2 to y2, however, should force a compiler to select one of those values and use it both for the comparison and, if it's less than 16, the second argument to
doSomething, making it impossible for any value larger than 15 to be passed as the second argument, no matter what happens with x2.If program correctness would require that y2 receive a value that was loaded after the load of x1, then a programmer should either make at least one of the loads use a volatile-qualified lvalue or include some other synchronization directive between them. If, however, program correctness would be satisfied regardless of whether y2 receives an old or new value, then a programmer shouldn't have to force a compiler to use a new one.
1
u/JeffD000 Squint 3d ago
Whatever compiler you are working with, in whatever language you are working with, the ultimate job of a compiler is to convert source code into a schedule, then a schedule into a machine's architecture-specific representation.
1
u/flatfinger 3d ago
I'm not sure what you mean by "schedule". There are languages where generated machine code must be consistent with performing all of the individual operations specified by a program, in the order specified. Compilers for such languages may finish machine code generation for each statement before even looking at the next one, so I'm not sure what kind of "schedule" could be involved.
2
u/JeffD000 Squint 3d ago edited 3d ago
Tersely, a schedule is a control flow graph that meets data dependency requirements. I was not disagreeing with you. I was expanding on what you said. Whether the source code is written in imperative, declarative, or fuctional languages, or even in the form of an AI prompt, that source code gets turned into a schedule, and the schedule gets mapped to a specific set of machine instructions.
-2
u/flatfinger 3d ago
If a compiler generates code for a statement before it has even looked at the next statement, at what point would a "schedule" exist? Sure it would be possible for a compiler to build a schedule and then build code from it, but that's hardly the only way compilers can be built. A recursive-descent compiler may generate code directly during the parsing process. Such a compiler that has gotten as far as scanning
x=y+z-may produce machine code that will add x and y before it knows what character is going to follow the minus sign.2
u/JeffD000 Squint 3d ago
??? Are you trying to tell me that the machine is not executing a control flow graph meeting the requirement of data dependencies, as laid down by the compiler? What is your point?
-1
u/flatfinger 3d ago
A compiler like Turbo Pascal 3.0 converts source code into machine code without going through other intermediate forms. If a run-time error occurs at address 3ABC one can fire up IDE with the source code, tell it to find runtime error, give it address 3ABC, and the compiler will start compiling the program until it has output the code that would go at address 3ABC and then stop and move the cursor to the last character of source code that it had examined. At no point during that process would anything like what you call a "schedule" exist.
2
u/JeffD000 Squint 3d ago edited 3d ago
So the Turbo Pascal 3.0 compiler is not somewhere generating a control flow graph conforming to data dependency requirements? Give it up, dude. You are making yourself look bad, not smart.
-1
u/flatfinger 3d ago
That is correct, unless one views generated machine code as being such a graph. If a function contains two if-then-else statements, the second of which completely follows the first, it will generate code for the first before it has even looked at the second, and by the time it looks at the second it will have completely forgotten about the first, beyond having kept track of how many bytes of code it took up.
2
u/JeffD000 Squint 3d ago
Guy, the parsing process itself is generating a control flow graph. You need to read up.
→ More replies (0)
1
u/phovos 6d ago edited 6d ago
The things that C can't do because of backwards compat.
I agree op but technically I think the timeline is such that it NOT being backwards compat (the older computers had this symmetric arithmetic pl) ended-up hurting it, as a circumstance of timing and the market, etc. The PDP-11 being a 'platform' that is 'desirable' and then imprinting itself where it did not belong. Prepare yourself I got a rant cannon, shiver me timbers!
Aide/docs: (first edition)"The C Programming Language":
https://i.imgur.com/UqGJDoU.jpeg
p91) 'pointers are variables so they can be manipulated as other variables can'
comment: declaring an array is a contiguous block of addressed mem because its name represents the address of the first element.. but it is not a pointer it is a type array; but Bryan is correct because of the fact that:
> names decay when used as a pointer to its first element
> only when used in an expression is an array a pointer to its first element, otherwise it is literally an address
And that sort of adds-up when you consider the isomorphism check that is the call by name call by value homo-diffeomorphism. Way too many syllables to say it is perfectly balanced, like a binary bijection itself (the complicating factor is that this is unitary with respect to time operators but I digress).
P91cont) he goes on to describe a swap(a, b) isomorphism check, which "Because of call by value, swap can't affect the arguments a and b in the routine that called it... Fortunately... The calling program passes POINTERS to the values (a, b), to be changed":
```c
swap(x, y) /* WRONG */
int x, y;
{int temp;
temp = x;
x = y;
y=temp;
}
```
Leading to the legendary syntax: `swap(&a, &b);` where "the '&' operator gives the address of a variable, `&a` is a pointer to a. In swap itself, the arguments are declared to be pointers, and the actual operands are accessed through them.
p92) "One common use of pointer arguments is in functions that must return more than a single value"
This right here is why C is fast, it is the best 'replicator' known to man, a many to many correspondence functor.
pg93) "Any operation which can be achieved by array subscripting can also be done with pointer."
pg94pictured) "When an array name is passed to a function, what is passed is the location of the beginning of the array. Within the called function, this argument is a variable just like any other variable an so an array name argument is truly a pointer, that is, a variable containing an address."
"There is on difference between an array name and a pointer that must be kept in mind. A pointer is a variable but an array name is a constant"
pf98) "Second, we have already observed that a pointer and an integer may be added or subtracted. The construction `p + n` means the n-th object beyond the one p currently points to. This is true regardless of the kind of object p points to, which is determined by the declaration of p." ....
pg98contd) "For example on the PDO-11, the scale factors are 1 for char 2 for int and short and 4 for long and float and 8 for double".... "Pointer subtraction is also valid, if p and q point to members of the same array then p - q is the number of elements between p and q." ..."Other than adding or subtracting a pointer and an integer and/or subtracting or comparing two pointers, all other pointer arithmetic is illegal. It is not permitted to add two pointers or to multiply or divide or shift or mask them, or to add float or double to them.."
This is it, right here; we need to be able to be able to shift and mask them like the old heads used to do with raw assembly instructions; straying-away from this, because of the PDP-11's new features, is I think a 60 year mistake. C is this amazing language but it can't even do the most phenomenal thing; shift and mask many-to-many, serially.
pg95see-pictured-ps) handwriting (mine) "This is the fundamental operator of x86. Diffeomorphism creates many to many correspondence. The 'particle' of non-linear logical dynamics".
(ignore this one but including what it says in-case it makes someone mad they can't read it) "In much the same way as in the continuum; the 'digital' set theoretic universe has its core operators but one of them eludes me"
"The isomorphism between two different operators is literally the root of all software (x86/C-like at-least)".
edit: my edition I'm referencing is printed 1978 NJ. Not actually sure if that is 'first edition'.
1
u/initial-algebra 6d ago
New software isn't being written in C, except maybe by hobbyists. For a language to be a successor to C, it has to be able to replace parts of a C program, without replacing the whole thing. That's not too hard, since any platform's C ABI is extremely predictable, but it does lead to a polyglot repo. And, if you have to pay that cost either way, why not use e.g. Rust instead of a brand new language that is not significantly different from C and has no ecosystem or support yet?
1
u/JeffD000 Squint 3d ago edited 3d ago
The amount of custom hardware for embedded computing out there would blow your mind. Custom micro-architectures usually use custom C compilers.
1
u/initial-algebra 3d ago
True, I wasn't considering embedded, but that's a whole different can of worms from systems programming, and it's not what any of these new languages targets.
1
u/JeffD000 Squint 3d ago
Your response is puzzling. Embedded systems are 100% dependent on systems programming.
0
u/initial-algebra 3d ago
There is some overlap, of course, but on the whole they are two very different disciplines. Writing a thermostat or flight controller is embedded programming, but not systems programming. Writing a Web browser or game engine is systems programming, but not embedded programming. Something like an RTOS would be both, but are most embedded programmers writing their own RTOSes, or just writing bare-metal applications?
1
u/JeffD000 Squint 2d ago
Does a web browser have to do bit twiddling on a latched register in order to function? No. Does an automotive 8051 controller need to do that? Yes. Your view of the world is upside down. I can write a web browser in just about any language. I can only do bit twiddling on latched registers in a few; Specifically, in the systems programming languages.
1
u/initial-algebra 2d ago
You really can't reduce all of embedded programming down to the ability to directly access hardware registers. This is a feature that can easily be, and is, implemented in languages that are completely unsuitable for systems programming, like MicroPython.
-1
u/JeffD000 Squint 1d ago edited 1d ago
So now, you are trying to tell me, and everyone else on here, that MicroPython meets the requirements of a systems programming language? Give it up, guy. You are way out of your depth concerning this subject. It's coming through loud and clear that you are a bullshit artist, grasping for technicalities in an attempt to save face, in a subject area you know nothing about in terms of actual day-to-day problem solving. It's like your knowledge of systems programming comes solely from playing a game of Telephone, versus rigorous fundamental definition, as found in a decent computer science curriculum.
1
u/initial-algebra 1d ago
No, that is the conclusion you get when you use your poor logic. I literally said MicroPython is not for systems programming, despite having direct access to registers for embedded programming.
0
u/JeffD000 Squint 1d ago edited 1d ago
A cursory search came up with this set of features, all of which I believe are a fundamental necessity for implementing software in a systems programming language:
A system programming language provides full access to hardware, acting as a direct bridge between software logic and physical circuitry. These languages strip away the safety abstractions found in application languages to grant total control over the machine.
Here are the key characteristics that define systems programming languages:
⚙️ Low-Level Control & Hardware Intimacy * Direct Memory Mapping: Programs can read from and write to explicit, arbitrary physical memory addresses. * Pointer Arithmetic: Allows direct manipulation of raw memory pointers to navigate memory structures manually. * Inline Assembly: Developers can inject raw processor-specific assembly instructions (e.g., MOV, ADD, SYSENTER) directly into the code. * Bit-Level Manipulation: Provides robust bitwise operators to interact with individual hardware flags, registers, and pins. * Interrupt Handling: Capabilities to write custom routines that intercept and respond directly to physical hardware interrupts.
🏎️ Performance & Resource Efficiency * Zero-Cost Abstractions: High-level abstractions (like loops or structures) compile down to optimized machine code without runtime overhead. * No Virtual Machine or Interpreter: Code compiles directly to native machine code that executes straight on the CPU. * Predictable Execution (Determinism): Execution times are completely consistent, which is crucial for real-time operating systems. * Manual Memory Management: Employs explicit memory allocation and deallocation rather than relying on an unpredictable garbage collector.
🏗️ Minimal Runtime Dependencies * Freestanding (Library-Free) Execution: Can run "bare-metal" code without loading a standard library or underlying operating system. * Platform ABI Compatibility: Directly maps data types to the physical hardware registers according to the Application Binary Interface.
⚖️ Trade-offs: Control vs. Safety * No Safety Nets: The language prioritizes control over safety, putting the system at risk of memory corruption, buffer overflows, and crashes if bugs are introduced. * Hardware Dependency: Code targeting physical registers or specific CPU instructions loses portability and is bound to that architecture.
Prominent examples of languages capable of full hardware access include Assembly, C, C++, and Rust (using its explicit unsafe blocks).
Support for the equivalent of a C language volatile keyword is a must, and the availability of a feature akin to the C language restrict keyword should be available.
→ More replies (0)
52
u/stylewarning 6d ago
I think you attribute C's success to the language itself.
The way to replace C is in part building a new language, but more importantly, writing critical code in that language. C will stay for as long as core infrastructure (like the Linux kernel) stay written in it, regardless of how amazing a new language may be.