In the world of programming languages it often feels like being stuck in a Groundhog Day-esque loop through purgatory, as effectively the same problems are being solved over and over, with previous solutions forgotten and there’s always that one jubilant inventor stumbling out of a darkened basement with the One True Solution™ to everything that plagues this world beset by the Unspeakable Horror that is the C programming language.
As the latest entry to pledge its fealty at the altar of the Church of the Holy Memory Safety, TrapC promises to fix C, while also lambasting Rust for allowing that terrible unsafe
keyword. Of course, since this is yet another loop through purgatory, the entire idea that the problem is C and some perceived issue with this nebulous ‘memory safety’ is still a red herring, as pointed out previously.
In other words, it’s time for a fun trip back to the 1970s when many of the same arguments were being rehashed already, before the early 1980s saw the Steelman language requirements condensed by renowned experts into the Ada programming language. As it turns out, memory safety is a miniscule part of a well-written program.
It’s A Trap
Pretty much the entire raison d’être for new programming languages like TrapC, Rust, Zig, and kin is this fixation on ‘memory safety’, with the idea being that the problem with C is that it doesn’t check memory boundaries and allows usage of memory addresses in ways that can lead to Bad Things. Which is not to say that such events aren’t bad, but because they are so obvious, they are also very easy to detect both using static and dynamic analysis tools.
As a ‘proposed C-language extension’, TrapC would add:
- memory-safe pointers.
- constructors & destructors.
- the
trap
andalias
keywords. - Run-Time Type Information.
It would also remove:
- the
goto
andunion
keywords.
The author, Robin Rowe, freely admits to this extension being ‘C++ like’, which takes us right back to 1979 when a then young Danish computer scientist (Bjarne Stroustrup) created a C-language extension cheekily called ‘C++’ to denote it as enhanced C. C++ adds many Simula features, a language which is considered the first Object-Oriented (OO) programming language and is an indirect descendant of ALGOL. These OO features include constructors and destructors. Together with (optional) smart pointers and the bounds-checked strings and containers from the Standard Template Library (STL) C++ is thus memory safe.
So what is the point of removing keywords like goto
and union
? The former is pretty much the most controversial keyword in the history of programming languages, even though it derives essentially directly from jumps in assembly language. In the Ada programming language you also have the goto
keyword, with it often used to provide more flexibility where restrictive language choices would lead to e.g. convoluted loop constructs to the point where some C-isms do not exist in Ada, like the continue
keyword.
The union
keyword is similarly removed in TrapC, with the justification that both keywords are ‘unsafe’ and ‘widely deprecated’. Which makes one wonder how much real-life C & C++ code has been analyzed to come to this conclusion. In particular in the field of embedded- and driver programming with low-level memory (and register) access the use of union
is widely used for the flexibility it offers.
Of course, if you’re doing low-level memory access you’re also free to use whatever pointer offset and type casting you require, together with very unsafe, but efficient, memcpy()
and similar operations. There is a reason why C++ doesn’t forbid low-level access without guardrails, as sometimes it’s necessary and you’re expected to know what you’re doing. This freedom in choosing between strict memory safety and the untamed wilds of C is a deliberate design choice in C++. In embedded programming you tend to compile C++ with both RTTI & exceptions disabled as well due to the overhead from them.
Don’t Call It C++
Effectively, TrapC adds RTTI, exceptions (or ‘traps’), OO classes, safe pointers, and similar C++ features to C, which raises the question of why it’s any different, especially since the whitepaper describes TrapC and C++ code usually looking the same as a feature. Here the language seems to regard itself as being a ‘better C++’, mostly in terms of exception handling and templates, using ‘traps’ and ‘castplates’. Curiously there’s not much focus on “resource allocation is initialization” (RAII) that is such a cornerstone of C++.
Meanwhile castplates are advertised as a way to make C containers ‘typesafe’, but unlike C++ templates they are created implicitly using RTTI and one might argue somewhat opaque (C++ template-like) syntax. There are few people who would argue that C++ template code is easy to read. Of note here is that in embedded programming you tend to compile C++ with both RTTI & exceptions disabled due to the overhead from them. The extensive reliance on RTTI in TrapC would seem to preclude such an option.
Circling back on the other added keyword, alias
, this is TrapC’s way to providing function overloading, and it works like a C preprocessor #define
:
void puts(void* x) alias printf("{}n",x);
Then there is the new trap
keyword that’s apparently important enough to be captured in the extension’s name. These are offered as an alternative to C++ exceptions, but the description is rather confusing, other than that it’s supposedly less complicated and does not support cascading exceptions up the stack. Here I do not personally see much value either way, as like so many C++ developers I loathe C++ exceptions with the fire of a thousand Suns and do my utmost to avoid them.
My favorite approach here is found in Ada, which not only cleanly separates functions and procedures, but also requires, during compile time, that any return value from a function is handled, and implements exceptions in a way that is both light-weight and very informative, as I found for example while extensively using the Ada array
type in the context of a lock-free ring buffer. During testing there were zero crashes, just the program bailing out with an exception due to a faulty offset into the array and listing the exact location and cause, as in Ada everything is bound-checked by default.
Memory Safety
Much of the safety in TrapC would come from managed pointers, with its author describing TrapC’s memory management as ‘automatic’ in a recent presentation at an ISO C meeting. Pointers are lifetime-managed, but as the whitepaper states, the exact method used is ‘implementation defined’, instead of reference counting as in the C++ specification.
Yet none of this matters in the context of actual security issues. As I noted in 2024, the ‘red herring’ part refers to the real-life security issues that are captured in CVEs and their exploitation. Virtually all of the worst CVEs involve a lack of input validation, which allows users to access data in ‘restricted’ folders and gain access to databases and other resources. None of which involve memory safety in any way or form, and thus the onus lies on preventing logic errors, solid input validation and preventing lazy or inattentive programmers from introducing the next world-famous CVE.
As a long-time C & C++ programmer, I have come to ‘love’ the warts in these languages as well as the lack of guardrails for the freedom they provide. Meanwhile I have learned to write test cases and harnesses to strap my code into for QA sessions, because the best way to validate code is by stressing it. Along the way I have found myself incredibly fond of Ada, as its focus on preventing ambiguity and logic errors is self-evident and regularly keeps me from making inattentive mistakes. Mistakes that in C++ would show up in the next test and/or Valgrind cycle followed by a facepalm moment and recompile, yet somehow programming in Ada doesn’t feel more restrictive than writing in C++.
Thus I’ll keep postulating that the issues with C were already solved in 1983 with the introduction of Ada, and accepting this fact is the only way out of this endless Groundhog Day purgatory.
Um… what? No. Not really. Some cases are easy to detect with some analysis tools. This becomes painfully obvious on any non-trivial C(++) project.
Chromium’s “memory safety” webpage claims:
Maybe someone should tell them that these are so obvious to detect…
JuSt ReWrItE iT In RuSt, iT’s MeMoRy SaFe Br0 🤡🤡🤡🤡🤡🤡🤡🤡🤡🤡🤡🤡🤡🤡🤡🤡🤡🤡🤡🤡🤡🤡🤡🤡🤡🤡🤡🤡🤡🤡🤡🤡🤡🤡🤡🤡🤡
I mean, yes, that seems to be the solution that many in the industry choose.
I certainly don’t see why they don’t leave the language alone. Goto and Union have been useful over the years in some specific scenarios (why the commands are there). Just leave it alone for backward compatibility. Of course, adding functionality is fine.
To play devil’s advocate: That mindset is how we got C++.
Ah yes, another of the “I like stone axes, so instead of using bronze axes all we need to do teach other users to use stone axes more safely” contentions. Expect more to surface over the next few years.
Good luck analysing typical large C/C++ programs containing multiple arbitrary libraries to determine the absence of pointer aliasing. I invite those who believe it practical to hold their breath while it is being done.
Lol. I don’t get why we’re constantly foregoing education for “this will do it for you”. Look at “memory safe” languages, they don’t even address how memory works. And yeah, I get working on large projects you can miss a free or not do a check on a input. My point being is, most non mem safe stuff are OLD. Shit that’s had base code remain the same for over at least 10 years.
“Why don’t you just write fewer bugs?” writ large.
So, what’s a modern best-practice way of handling things that unions are so good and efficient for? (and why are they “unsafe”, anyway?)
Why are they unsafe? They inevitably implode, and before that cause great damage – see Soviet Union for an example.
So you’re saying the implosion of the EU and USA are inevitable? Good to know in advance.
Meltdown syndrome alarm needed here.
Tagged unions. Compiler can ellide checks when it can prove to itself the current tag at the compile time. That needs to be able to prove absence of aliasing.
There is tagged union in C++ …it is implemented… using normal union.
Thing is, who would provide safety of all these black boxes added to “safe” language to facilitate it’s fun tionality? Who would prove it being portable to dozens of platform?
All type-safe languages currently are monopolies of their developers. It’s actually a bigger safety breach than any buffer overflow – it’s supply chain’s safety problem.
VM instructions. Bare unions are an essential part of efficient VMs, usually some (but often, not all) instructions will have a tag, but it can be oddly-sized, like 5 bytes, to leave more room for the operation. Using a bare union allows for complete control of the memory layout of the instruction, and the performance gains are real. Same thing with the control flow offered by goto, it isn’t feasible to write an optimal VM in C without using it. Zig recently added labeled continue, and has several other control flow constructs which C does not, and can now be used to write a quality VM without goto. TrapC appears to lack that sort of thing, and therefore, cannot be.
Rust was designed with a focus on correctness, not memory safety.
Eliminating vast swathes of memory bugs was a side effect.
But that would be hard to argue with, so instead everyone with some weird language confirmation bias jumps on an invented claim.
Unions are really useful in embedded systems when you want to overlap different structures on say a a protocol stream.
Goto correctly used allows you to concentrate the exit code where you want to release resources in a consistent manner. The alternative is deeply nested code with multiple exit points and deeply illegible code (Don’t get me started on single return MISRA rules either)
All languages syntax can be misused. But just because someone has used Goto to write spaghetti in the past, does not mean that it is bad for everyone, or they don’t have a use
If you are going to fix C, look at all the variations generated by various compiler pragmas and standardize them, such as structure packing
MISRA C for the most part is helpful, but it has some serious flaws that make code less reliable.
The worst one (at least in the edition I was looking at) is that field names of structs should be unique. It’s a major obstacle to modularity and forces coders to come up with convoluted names within structs. It’s also a denial of the principle of locality, so it’s scientifically unsound.
Ultimately, MISRA C isn’t truly open to public empirical critique: it’s snake oil.
Neither Rust or C can be totally secure. Anyone who can crash or stop a program while its running can steal information or cause damage. No amount of development can mitigate glitches, hardware crashes, cosmic ray cpu issues, etc. Fringe cases, but lambasting C over memory safety misses the point. Does the US gov really want me to not learn C by writing a hello world? Is that possibly dangerous? I promise I wont write drivers or OS components.
We could have significantly better reliability across the entire software stack than we currently do without having to tackle hardware failures.
So, why is RUST better than ADA?
This feels like the modern: “This thing is unsafe! Instead of teaching people how to us it safely, we need to replace it with something more limiting for their sake.”
No, thanks. Leave C/C++ alone. They are fine as-is. Use a different language if you do not like its features.
Sometimes I wonder if any of these “C is fine” folks actually work with other software engineers?
I mean like REAL software engineers, dealing with the dirty, ugly reality of software; Not academics arguing about perfect world scenarios on Reddit.
Have they met the idiots with CS degrees coming out of Uni these days? It’s fine that an expert programmer can use C/C++ without creating exploitable bugs using tooling and other clever tricks, but being real, most programmers cannot be bothered to put in that kind of effort. So much code is written by half-assed and/or overworked coders. The agile methodology that everyone is so on about rewards function and delivery over quality of form. No one cares if there’s a hidden memory safety violation. No one is looking for it. Bake it into the damn languages already.
That’s my take at least. All this academic BS on the topic fails to address human laziness, ineptitude and the unending pressure from above to “just make it work” from stakeholders.
You forgot to mention those AI-copy-paste-coding solutions. “Just use some code that a hallucinating AI found for you without understanding what it does.”
As usual: to make it good it needs time, experience, reviews, reviews, tests, time and repeats.
Speaking of that … Today it is nice to ‘search’ for some howto use a function or some such as it is usually on-line. Before if it wasn’t in a book, we had to figure it out ourselves. Now the up-to-date information is at our finger tips. In that sense, we live in a better world for speeding up programming. I don’t mind finding code snippets/tutorial that someone has written (don’t/won’t use AI) to help a project. You know what you want, and seeing the code (or write-up) you can tell if it will do what you are looking for or not. Yes understanding is key. No free lunch here (in my pea brain anyway).
The copy-paste culture of coding predates AI coding by at least a decade or two. I think maybe we have trash coders now. Perhaps we shouldn’t have encouraged everyone and his dog to learn to code, and to immigrate to do so
There are a lot more programmers than there once were, and the education of these programmes is far less curated. When “just learn how to program” is touted for two decades as a way to get a job in a recession the quality will not be raised either.
I did. C was fine. The cat’s meow so to speak. We had a fine team back in the 80s, 90s, 2000s. We programmed our SCADA control systems (masters and RTUs) in C, Assembly (real-time kernel applications) and on the desktop side (database, UI stuff) it was C and Pascal (Delphi later). I headed up a group of 8 people. Got along good. They were CS professionals with good backgrounds in physics/math/electrical and of course data structures and knew C. Yes we did mentor the newbies of course. But the hiring process weeded out a lot of would be programmers (as did just looking at school transcripts before getting them in for interviews. By that time we were doing code reviews, and the full documentation on new features (specification, design, and testing) before coding began (for the most part as we were flexible depending) which slowed things down, but was necessary. All had to be signed off my me and management directly above me before project was complete. Also all programmers were ‘in the office’ working as a team… None of this work from home garbage of today.
Sounds super brah, brah! Go with Christ. Hate that garbage. That’s the problem you know, garbage.
You know a lot of work from home pressure is from lack of pay and medical coverage from employers, contracting “gigs” at the scale of entire fly by night companies. That’s where your garbage comes from, corporate management.
“Have they met the idiots with CS degrees coming out of Uni these days?”
Yeah, and the one thing I can say with confidence is there ain’t no magic programming language making their stuff safe, anyway.
I think it’s just not fair for them to ruin it for the rest of us. I love programming, probably more than anything else in the world, and it sucks to see this field and community seemingly being attacked by all sorts of malicious actors, be it the copy-pasters that are destabilizing and enshittifying the whole ecosystem, the evangelists that get off by forcing others to solve problems in a certain way or the CoC warriors that try to rip governance out of the hands of the very people that built all the little components that make up 90% of the modern software stack.
NSA had a requirement in the 1980s that only their secure app could be running on a hardware platform. All other binaries must be removed.
Is a multitasker part of any secure app?
Uh…which app? The NSA has LOTS. There are a lot of multitasking systems running trusted software environments.
I assume in the 1980s there WAS only one, which is why this whole thing is moot because it’s not the 1980s anymore where you would run a computer with no HDD off a floppy drive containing a single program
By the 80’s there were hundreds, if not thousands, do you mean the 60’s? Multitasking systems that ran on punch cards are a thing.
This will be DoA bc of the union removal. That’s a key feature for C. It’s not “widely deprecated,” it’s used ALL OVER C projects, from utilities to OSs to embedded.
It has so many use cases from APIs to performance improvements , e.g. casting a struct that fits into 64-bits to an integer to perform certain operations quickly on it.
It won’t take off for that reason alone. Article puts it clearly:
This could’ve been neat, but they made one extremely stupid decision.
Goto is literally called out in Linux kernel coding conventions, too, and I agree with them: it’s way more readable for resource handling in failure cases.
Ez: don’t fail like a loser, just win /s
Also agree; where exceptions+RAII isn’t an option, goto usually has to be used.
I feel like providing some level of ‘backing evidence’, so:
Otherwise, the code actually gets more error-prone, as it’s very easy to mess up the cleanup process. Otherwise code tends to look like:
a = initThingA();
if (!a) { return OOPS; }
b = initThingB();
if (!b) { freeThingA(a); return OOPS; }
c = initThingC();
if (!c) { freeThingA(a); freeThingB(b); return OOPS; }
There’s probably a rule of programming about this. Perhaps: “in a correct function where goto and RAII are both not possible, the number of ‘free’ statements will usually increase with exponential growth relative to the number of fallible resource creations, or the function will resemble a pyramid”.
There is a way to somewhat cheat this when the code is returning a new struct of some kind, by designing the struct, setup code, and cleanup code such that the incomplete object is always ‘complete enough to destroy’; code then becomes: if (!a) { destroyOuterThing(outerThing); return OOPS; }
Why not just use a library like https://github.com/Snaipe/libcsptr ? There is already attribute((cleanup)).
Also, you could create a boundary-checked array thing using structs, macros, fns, etc.
Also, taking out unions is just plain stupid. They’re not deprecated at all. They’re used all over the place!
It’s not just memory safety. It’s a very C-brained way to look at things if you only consider “am I accessing outside this array?” Rust is no magic bullet, but they at least learned that lifetimes are more important. There is a temporal element here, where accesses to bits of memory are valid or invalid at different times.
Not just whether something has been created/freed yet either. The reason rust tackles thread safety as well is threads can own bits of memory at different times and that ownership can change. Having two (or more) threads share memory read-only is fine but if one of them switches to writing that’s a problem, and the rules change on the fly as threads enter or exit different parts of a program. Rust works hard to formally prove that there is no window of time when invalid accesses are possible.
I can’t believe that any computer professional believes that memory safety is not a major security concern. Yeah there is also input and output validation but if you leak data into a publicly addressable area or even an area accessible by a different user, that is a problem and its been a common problem for a long time.
Another example of the nannyfying of all areas of society. Remove unions ? F… That
mostly agreed with this write up.
for me, the key that made it clear the ‘trapc’ folks don’t have a plan is they gave an example of supporting “std::cout<<“hello world”;” to illustrate how similar to C++ they are, with the footnote that it only works because they ignore “std::”.
that’s just such a haphazard way of designing a language, to randomly support a subset of (frankly awful) C++ practice just to give a vague feeling of familiarity. they are absolutely unserious about this. they have just decided to reinvent early C++, warts and all. why, yes, it is easy to add just a few features to C. no, that doesn’t produce a good language or development practice.
there are a ton of flaws in languages like rust or java but they are designed, they aren’t just a random collection of someone’s least-effort hacks to C.
trapC is to C++ as C– is to C, or ASIC was to BASIC. does anyone else remember C–? a small and extremely simple compiler for DOS that supported a language that wasn’t entirely unlike C. it was a testament to getting 90% of the functionality with 10% of the effort, but obviously also it was a dead end. it inspired me tons, but i don’t think i ever wrote a single useful program in it.
Worth mentioning memory safety implies automatic freeing of unused variables, which often leads to significant efficiency gains. More efficiency can enable more performance because less waste facilitates more concurrency.
Is the macho argument memory safety is about nannying really worth the runtime crashes and vulnerabilities in exchange for the privilege to need to manually optimize stuff LLVM can figure out better? Smells like copium in here
Runtime crashes are the minimal problem. Odd behaviour when code wasn’t executed but it’s result materialized, ISA-level bugs or logical errors are majority. Rewritten in Rust Linux utilities displayed all baby security issues Berkeley Unix versions of them had, reintroducing 1970s style of hacking, all because devs have no idea how things can be used in malicious way.
yeah i am a big fan of C so i do a lot of manual memory management and i’m mostly comfortable with it. and as much as i love to be released from that burden, the stop-the-world sort of garbage collection that seems to still be common in (for example) java just drives me crazy. but i have an anecdote to confirm what you say here
i ported a recursive (tree) data structure from C to ocaml once on a lark, just to see what it would be like. there were some things that ocaml forced on me that seemed to go against performance. like they only offer 31-bit integers because they use the top bit as part of their memory management, and in practice that meant i had to use only 16 bits. but writing the code was a delight, this particular data structure is really far better suited to ML than to C.
the thing that surprised me is that even with all the downsides, the ocaml version was a little bit faster than the C!! i’m fairly certain it’s because the C had my ad hoc implementation of reference counting, while ocaml had built-in reference counting. anyways, that’s my best guess. afterwards, i found a red forehead mistake in my manual reference counting and i suspect now my C code is as fast as the ocaml version but i haven’t gone back to check.
a lot of things can influence it but sometimes decent implicit memory management is actually faster than the kind of idiocy i’ll spew if i’m doing something unfamiliar.
so overall i’m not impressed with (for example) rust but i’d like to spend a weekend someday and really learn its memory model. there’s definitely a lot of room for improvement over malloc/free!
Going in the opposite direction, I’ve been recently playing around with the K&R, Megamax C for early Macs and Atari ST. It’s fun, and like playing with a pile of grenades!
More Ada article please!
It’s fine if you want to be an expert at using footguns safely, but there will always be novices writing production code. Give the novices fewer footguns, and give the experts escape hatches. I think we’re doing well in collectively heading towards this direction.