But I'm pretty skeptical, to be honest. I've been working on one or another high-performance C or C++ program for most of the past 20 years. I can't ever remember getting a really substantial speed improvement from upgrading the compiler for an important platform, because anything the old compiler did badly that really hurt program performance had already been avoided, either before or after seeing it in profiling results. I'm sure that if you took C code developed on a modern compiler and compiled it with a 90s compiler, it would be slower. But I doubt the software ecosystem would actually be drastically slower if optimizer technology hadn't advanced significantly since 1995, and everything had been developed under that constraint. And I don't think every single advance in optimization since 1995 is dependent on degenerate transformations of undefined behavior.
While benchmark games played a part in the modern ‘undefined behavior’, I'm not so sure that it would made much difference to adoption of the language. Consider Linus Torvald's well-known rant as a point in the opposite direction.
In the universe where ‘undefined behaviour’ had been clearly specified as ‘what you write is what you get’, C might have gone to consistent use of optimization-enabling annotations, following ‘const’ (and later ‘restrict’). Along those lines, ‘volatile’ was ANSI's other big mistake, as creating it broke existing code; I now think that should have been the default, with fragile-optimizable status being explicitly indicated by extending the use of ‘register’ keyword.
Look at how often folks on HN complain that the Web platform is useless because JS is slow. C without optimizations is just as slow if not slower. Compiler optimizations are so good that people don't realize how much they rely on them.
Linus was wrong when he complained about the compiler treating null dereference as undefined behavior. This is a very important optimization, because it allows the compiler to omit a lot of useless code in libraries. Often times, the easiest way to prove that "obviously" dead code can never be executed is to observe that the only way it could be executed is for null to be dereferenced.
Opt-in optimization keywords wouldn't scale to the sheer number of optimizations a modern compiler framework performs. Restrict hasn't been a success because it's too subtle of an invariant. It's the kind of thing compiler authors, not application developers, should be thinking about.
It's strange that the word "unsafe" has tainted people's thoughts so dramatically. Like calling torrenting music "piracy."
Or at very minimumm compiling with warning enabled as errors, with a continuous build breaking on static analysers errors, then yes.
Ditto for Bitcoin. It's the basis of a new financial system. The core software is written in C++.
Same for Linux. C.
Prejudice generally isn't helpful, and it's a bit strange that you can recognize C's merits while also decrying it.
C++ states stuff like references must not be NULL and "this" must not be NULL, but in the real world it is possible for a NULL pointer to be dereferenced into a reference and for a method to be called on that reference and for the method to complete execution without the app crashing. Yet, some C++ compilers are now insisting that "this == NULL" checks (which is the most hilarious case, but simple "&ref == NULL" are the same) and all the dependent code be entirely removed, hamstringing runtime safety and sanity checks.
What works for me is when the compiler says "for this to happen the code would have had to crash"; but what does not work for me is "for this to happen the code would have to be violating the specification" as the entire point of NULL checks in a program was always to check for invalid execution and mitigate its effects :/ and yet since this code has never crashed on any reasonable C++ compiler the only way to check for it is to add comparisons that are now being removed under some misguided assumption that the code would fail at runtime.
I feel like there's a huge disconnect here. Even after the strange behavior is explained, some people say "wow, I never ever want that behavior, how do I reliably avoid it?" but others respond "there's no problem, you're just using it wrong".
Is there really no way to satisfy both sides?
I haven't actually seen anyone complaining about that. Do you have any links?
There are some specific complaints like: JS can't do 64-bit arithmetic or SIMD; but that's only really needed for games and scientific computing, which don't need to use JS. Or that JS is single-threaded; that's a fundamental feature of its design, nothing to do with optimisation.
C without optimisations is just as slow if not slower.
Nobody's talking about taking away all optimisations, just not trying to do extreme optimisations that exploit undefined behavior (or rather, assume it can never occur).
Plenty of C compilers worked that way in the 90s and performance was perfectly acceptable (on hardware with a fraction of the speed and memory of today's computers and phones).
Modern C++ probably relies on a higher level of optimisation, but that's another story.
Those "extreme" optimizations are usually just surprising behavior that emerges from perfectly reasonable optimizations. For example, assuming that code that follows null dereference is dead is important.
> Plenty of C compilers worked that way in the 90s and performance was perfectly acceptable (on hardware with a fraction of the speed and memory of today's computers and phones).
You can get that experience with GCC -O0. Do you want to run code like that? I don't, and neither do customers.
People who don't work on modern compilers often think that there is a subset of "simple" optimizations that catches "obvious" things, and optimizations beyond that are weird esoteric things nobody cares about. That isn't how it works. Tons of "obvious" optimizations require assumptions about undefined behavior.
You're implying that 90s compilers had no optimisations, which is incorrect.
Why is this a hot topic now, and why was it not a hot topic 10 or 15 years ago?
I suggest that something changed in the interim, and that what changed is the addition of dangerous optimisations. I'm not sure where it all started but strict aliasing in GCC is a potential candidate.
As others have pointed out, GCC and Clang seem to have by far the most horror stories, even though they don't actually generate the fastest code. I imagine that's mostly because GCC and Clang are so widely used, though.
As the article states, if a local static function has exactly one assignment to it, then it can be an important optimization to assume that it will always have that value. Imagine that it's some kind of "DebugPrintf(...)" function that, in release builds, is always set to a no-op that does nothing before being called. You would definitely want that indirect function call to be inlined to nothing in release.
It is a (decades ago) Solved Problem.
It seems to me that C had already won as the de facto systems language, long before any of these "modern optimisation techniques" cropped up.
Optimisations that make it harder to use the language safely are downright dangerous in my book.
But better yet would have been Modula-3 or Active Oberon.
Quite helpful when maintaining unknown code in big corp projects.
How come we have languages like Rust that achieve almost the same speed while maintaining dramatically better safety, anyway?
C would be dead.
We will have a sound low-level language suitable for optimization.
Now, if we throw in C++ eradrication...