There was already a legal way to achieve this that everyone should already have been using (laundering a pointer through a no-op memmove). Using reinterpret_cast here is a bug.
The "start_lifetime_as" facility does one additional thing beyond providing a tidy standard name for the memory laundering incantation. Semantically it doesn't touch the memory whereas the no-op memmove intrinsically does. In practice, this makes little difference, since the compiler could see that the memmove was a no-op and optimized accordingly.
No because the object does not exist after std::launder. It only exists after std::start_lifetime_as. The bytes being there says nothing about the object, per the C++ standard.
The compiler will create an implicit lifetime type at the memmove destination as required to give it defined behavior. Technically you don't even need std::launder, it is just far more convenient than the alternative.
The cppreference description seems questionable to me:
> Implicitly creates a complete object of type T (whose address is p) and objects nested within it. The value of each created object obj of TriviallyCopyable type U is determined in the same manner as for a call to std::bit_cast<U>(E) except that the storage is not actually accessed, where E is the lvalue of type U denoting obj. Otherwise, the values of such created objects are unspecified.
So T is the complete new object. It contains subobjects, and one of those subobjects has type U. U is initialized as if by bit_cast, and I presume they meant to say that bit_cast casted from the bits already present at the address in question. Since “obj” is mentioned without any definition of any sort, I’ll assume it means something at the correct address.
But what’s E? The page says “E is the lvalue of type U denoting obj,” but obj probably has type char or a similar type, and if it already had type U, there would be no need for bit_cast.
Well, ignoring alignment restrictions, it depends on the implementation of read. If it is truly opaque, as far as the compiler is concerned, the kernel (or the network card or whatever) is truly constructing a Foo in that buffer, making the cast perfectly legitimate.
start_lifetime_as is useful when the buffer lifetime is transparent to the compiler and it can mess up aliasing assumptions.
> An object of dynamic type Tobj is type-accessible through a glvalue of type Tref if Tref is similar ([conv.qual]) to: Tobj, a type that is the signed or unsigned type corresponding to Tobj, or a char, unsigned char, or std :: byte type. If a program attempts to access ([defns.access]) the stored value of an object through a glvalue through which it is not type-accessible, the behavior is undefined.
The dynamic type of a char buffer is, well, a char buffer, and can only be accessed via things that are the same type as a char buffer up to signedness and cv-qualification. The actual strict aliasing rules are not commutative!
This gets to the heart of effective type rules, which are complex, confusing, and not properly implemented by compilers. C and C++ definitely diverge here, because C is less ambitious in its object model (which mean it just simply leaves so many details about it undiscussed).
Malloc returns memory that is uninitialized and has no type. The effective type of that memory is initialized on first use by C, whereas C++ relies on angelic nondeterminism to magically initialize the type at return type to whatever will work in the future.
> Malloc returns a buffer and then you cast it to the type you want.
You can’t cast the buffer — you’re casting the pointer.
One might argue (I’m not sure whether this is correct) that malloc allocated an array of ints and the language merely has no way to state that directly. Then you write to those ints using a char pointer, and then you access them as ints, but they’ve been ints ever since allocation.
If the type is an implicit-lifetime type, then you can legally create an unsigned char array, and then reinterpret_cast a pointer to that to a pointer to the type.
https://eel.is/c++draft/intro.object#15 is an example showing this with malloc; the subsequent paragraph further permits it to work with an unsigned char array.
Large projects have been going to regular scheduled releases for a long time. Until the 90's people thought they could waterfall a large release with all your desired features (and for tiny projects this is still a good idea), but as your projects grow (possibly just to small) you reach a point where someone is always working on a feature that isn't ready yet, so a regular release means you still can support your customers with releases. This forces developers who are unsure they will be ready to have some sort of "disabled this unstable feature" toggle, which is about the best you can do.
Yup. OpenJDK is one of the best success stories of this.
Up until Java 9, they would release once features were complete. But that meant there were years between the 7 and 8 release and even more years between the 8 and 9 release.
The industry had gotten into the habit of always running old versions of Java (my company was on 6 for an uncomfortable amount of time. But others have had it worse).
More frequent smaller releases has gotten companies more into the habit of updating frequently which also, very helpfully, gives devs new features frequently.
Sorry to derail...but that brought up some bad memories. The networking company Wellfleet (became Bay Networks thru merger with Synoptics, then died as a chunk of Nortel Networks) had a management tool called 'Site Manager' (SM, aka 'Site Mangler' aka 'S&M').
SM was a monstrous Java app that papered over the (horrifying) fact that everything on a Wellfleet router was configured with SNMP (full-body shiver). Oh, there was a CLI, but even a hard-core CLI pilot like myself couldn't face stuff like "set wflplnterfaceEntry.2.192.168.10.10.3 1" all day long.
Wellfleet clearly employed no software engineers, only monkeys that hammered on keyboards and piled cruft upon cruft to the SM codebase. The end result was that every release of Wellfleet device code (down to point releases) relied on a particular version of SM, which, of course, relied on a particular version of Java.
Now, since virtually no site over a certain size could count on every device running the same version of code, you had to be able to switch between a couple of versions of Java to run a given version of SM. And, as a consultant to Wellfleet shops, I had to be able to run all of them. I got really good at multibooting Windows, but in the end I had a 'Wellfleet' laptop modified a bit so I could easily pop it open and swap disks, each one for a different version of SM running on a different version of Java.
The Linux kernel is another example. The 2.5 development cycle (which led to the stable 2.6 series) was brutally long, and distros resorted to back-porting new features into their own kernels based on the stable 2.4 series that they provided to their users, creating all kinds of excitement. After 2.6.0 was released, Linus basically went nope, not gonna do that again.
And this is why I prefer a "hybrid" version scheme between CalVer & SemVer: A series number to allow LTS releases, a period, a hyphenated datestamp, optional "flag" letters "B" for breaking changes, "S" for security fixes, "F" for new features (more than one can be present, always in that order), and if more than one release is made on the same day the second & subsequent releases get a period followed by a release number starting from 1.
E.g. 3.2026-05-01FS for a first release of the day with a new feature & security fix to release series 3, 2.2026-05-01S backporting the security fix to release series 2 (LTS, say), 3.2026-05-02 for a "bug fixes & performance improvements" release, 3.2026-05-02S.1 for a security fix to the "bug fixes & performance improvements" release.
Like SemVer, this lets users know when there are breaking changes to an API or such, which releases contain security fixes, which releases have new features, and allows multiple simultaneous release versions for LTS support. Like CalVer it lets users know how recent a release is, and makes it pretty easy to figure out what the release schedule was historically (and likely still is) by comparing versions.
Are there any large projects that are older than 25 years that didn't go through that? The time based release thing as been recommended for about 25 years (can anyone get real data?), so newer projects may just be using it without knowing the pain, but for older projects it seems like they all went through it at least once.
For many years that was the only version that could be used. What become gcc3 took years. In the end it was better, but for a while gcc 2.95 was the best we had despite the bugs.
Yeah, GCC’s recent major releases have been remarkably regular, much like Fedora’s spring releases, and their releases seem to fit into the same broader rhythm. Hint? Red Hat.
That is why egcs was launched, to get around the inability of the old team to do gcc releases. The issues had little to do with ideology and were about fixing a broken process and replacing it with something that had a hope of working.
I've already been using it for some time (debian sid has a trunk package). it has c++26 reflection, so I already do some magical things with reflection (much better for some cases e.g. for ser-des).
I only wish they had a lsp server in their eco-system!
> The so-called "json" format for -fdiagnostics-format= has been removed in this release. Users seeking machine-readable diagnostics from GCC should use SARIF.
But:
> GCC can now output diagnostics in HTML form via -fdiagnostics-add-output=experimental-html
I wonder what drove the decision to remove JSON output and add HTML output?
Noob question - Does gcc use LLVM anywhere under the hood or it has its own code generation and optimization pipeline? How does it stack in comparison to LLVM?
Let me wikipedia that for you :-) Like people mentioned already, gcc predates LLVM by quite some years, ie. Wikipedia mentions March 22, 1987 for gcc, while LLVM's initial release was 2003.
A big difference between the 2 is also the license. GCC uses the GPL while LLVM uses Apache License, hence the projects don't share code.
> Does gcc use LLVM anywhere under the hood or it has its own code generation and optimization pipeline?
No it doesn't use LLVM
> How does it stack in comparison to LLVM?
Well it support more targets then LLVM and in most cases it generates similar if not better executable.
The other answers (NO!) are correct, but... there was a gcc plug-in to use the llvm backend with gcc. Apple used llvm-gcc (circa 2012; gcc front end, llvm back end) while transitioning from gcc to llvm.
The libsanitizer library is imported from the LLVM sources into the GCC source tree. There are probably more examples of code sharing. Both projects are quite large.
I tried the unstable sources for a while, in the last ~3 months. I ran into some issues with some programs (could not compile them with recent GCC, but older GCC worked fine), so gcc 15.x works better for me in general (presently) - but from, say, +3000 programs to compile, the vast majority works well, and a few may need patches (which can often be found in LFS/BLFS by the way, they often use sed instructions to fix individual things and then it works usually).
Hopefully they fixed those issues. We all need stability and things-to-work.
I want to point out an implemented feature that people SHOULD be adopting but that I doubt will be picked up:
This is for "std::start_lifetime_as<T>". If you have not heard of this before, it's the non-UB way to type-pun a pointer into a structured type.Nearly all zero-copy code that deals with external I/O buffers looks something like:
With this merged, swap the reinterpret_cast for start_lifetime_as and you're no longer being naughty.https://en.cppreference.com/cpp/memory/start_lifetime_as
There was already a legal way to achieve this that everyone should already have been using (laundering a pointer through a no-op memmove). Using reinterpret_cast here is a bug.
The "start_lifetime_as" facility does one additional thing beyond providing a tidy standard name for the memory laundering incantation. Semantically it doesn't touch the memory whereas the no-op memmove intrinsically does. In practice, this makes little difference, since the compiler could see that the memmove was a no-op and optimized accordingly.
This still has unresolved alignment issues that blow up outside the amd64 ecosystem.
Is this just a basic lack of alignment enforcement or is there a bigger issue?
No because the object does not exist after std::launder. It only exists after std::start_lifetime_as. The bytes being there says nothing about the object, per the C++ standard.
The compiler will create an implicit lifetime type at the memmove destination as required to give it defined behavior. Technically you don't even need std::launder, it is just far more convenient than the alternative.
The cppreference description seems questionable to me:
> Implicitly creates a complete object of type T (whose address is p) and objects nested within it. The value of each created object obj of TriviallyCopyable type U is determined in the same manner as for a call to std::bit_cast<U>(E) except that the storage is not actually accessed, where E is the lvalue of type U denoting obj. Otherwise, the values of such created objects are unspecified.
So T is the complete new object. It contains subobjects, and one of those subobjects has type U. U is initialized as if by bit_cast, and I presume they meant to say that bit_cast casted from the bits already present at the address in question. Since “obj” is mentioned without any definition of any sort, I’ll assume it means something at the correct address.
But what’s E? The page says “E is the lvalue of type U denoting obj,” but obj probably has type char or a similar type, and if it already had type U, there would be no need for bit_cast.
Well, ignoring alignment restrictions, it depends on the implementation of read. If it is truly opaque, as far as the compiler is concerned, the kernel (or the network card or whatever) is truly constructing a Foo in that buffer, making the cast perfectly legitimate.
start_lifetime_as is useful when the buffer lifetime is transparent to the compiler and it can mess up aliasing assumptions.
Your code is not only naughty, it’s also incorrect due to alignment issues.
You’re allowed to type pun char buffers.
No, you're not.
You're allowed to access any type via a char buffer. But the converse is not true (quoting https://eel.is/c++draft/expr#basic.lval-11):
> An object of dynamic type Tobj is type-accessible through a glvalue of type Tref if Tref is similar ([conv.qual]) to: Tobj, a type that is the signed or unsigned type corresponding to Tobj, or a char, unsigned char, or std :: byte type. If a program attempts to access ([defns.access]) the stored value of an object through a glvalue through which it is not type-accessible, the behavior is undefined.
The dynamic type of a char buffer is, well, a char buffer, and can only be accessed via things that are the same type as a char buffer up to signedness and cv-qualification. The actual strict aliasing rules are not commutative!
I’m not a language lawyer but i think the part you are missing is about “type establishment”. (Is this a C vs C++ thing?)
Malloc returns a buffer and then you cast it to the type you want. Similarly for all memory allocators.
Punning the same region of char buffer as two different types is a bit different.
This gets to the heart of effective type rules, which are complex, confusing, and not properly implemented by compilers. C and C++ definitely diverge here, because C is less ambitious in its object model (which mean it just simply leaves so many details about it undiscussed).
Malloc returns memory that is uninitialized and has no type. The effective type of that memory is initialized on first use by C, whereas C++ relies on angelic nondeterminism to magically initialize the type at return type to whatever will work in the future.
> Malloc returns a buffer and then you cast it to the type you want.
You can’t cast the buffer — you’re casting the pointer.
One might argue (I’m not sure whether this is correct) that malloc allocated an array of ints and the language merely has no way to state that directly. Then you write to those ints using a char pointer, and then you access them as ints, but they’ve been ints ever since allocation.
yes but that works not because malloc is special but because there are more relaxed types rules than suggested by the comment above.
If the type is an implicit-lifetime type, then you can legally create an unsigned char array, and then reinterpret_cast a pointer to that to a pointer to the type.
See https://eel.is/c++draft/intro.object#def:object,implicit_cre....
https://eel.is/c++draft/intro.object#15 is an example showing this with malloc; the subsequent paragraph further permits it to work with an unsigned char array.
1. You still need std::launder in that case.
2. It doesn't initialize the object that is implicitly created, even if the storage has initialized chars.
The paper that introduced the implicit lifetime mechanism suggests that std::launder is not required: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p05....
And should always use -fno-strict-aliasing anyways. The default rules are insane
Somehow I never realized that GCC has a very regular release schedule until looking it up just now: https://gcc.gnu.org/develop.html
Large projects have been going to regular scheduled releases for a long time. Until the 90's people thought they could waterfall a large release with all your desired features (and for tiny projects this is still a good idea), but as your projects grow (possibly just to small) you reach a point where someone is always working on a feature that isn't ready yet, so a regular release means you still can support your customers with releases. This forces developers who are unsure they will be ready to have some sort of "disabled this unstable feature" toggle, which is about the best you can do.
Yup. OpenJDK is one of the best success stories of this.
Up until Java 9, they would release once features were complete. But that meant there were years between the 7 and 8 release and even more years between the 8 and 9 release.
The industry had gotten into the habit of always running old versions of Java (my company was on 6 for an uncomfortable amount of time. But others have had it worse).
More frequent smaller releases has gotten companies more into the habit of updating frequently which also, very helpfully, gives devs new features frequently.
Sorry to derail...but that brought up some bad memories. The networking company Wellfleet (became Bay Networks thru merger with Synoptics, then died as a chunk of Nortel Networks) had a management tool called 'Site Manager' (SM, aka 'Site Mangler' aka 'S&M').
SM was a monstrous Java app that papered over the (horrifying) fact that everything on a Wellfleet router was configured with SNMP (full-body shiver). Oh, there was a CLI, but even a hard-core CLI pilot like myself couldn't face stuff like "set wflplnterfaceEntry.2.192.168.10.10.3 1" all day long.
Wellfleet clearly employed no software engineers, only monkeys that hammered on keyboards and piled cruft upon cruft to the SM codebase. The end result was that every release of Wellfleet device code (down to point releases) relied on a particular version of SM, which, of course, relied on a particular version of Java.
Now, since virtually no site over a certain size could count on every device running the same version of code, you had to be able to switch between a couple of versions of Java to run a given version of SM. And, as a consultant to Wellfleet shops, I had to be able to run all of them. I got really good at multibooting Windows, but in the end I had a 'Wellfleet' laptop modified a bit so I could easily pop it open and swap disks, each one for a different version of SM running on a different version of Java.
Good times...it was not.
The Linux kernel is another example. The 2.5 development cycle (which led to the stable 2.6 series) was brutally long, and distros resorted to back-porting new features into their own kernels based on the stable 2.4 series that they provided to their users, creating all kinds of excitement. After 2.6.0 was released, Linus basically went nope, not gonna do that again.
And this is why I prefer a "hybrid" version scheme between CalVer & SemVer: A series number to allow LTS releases, a period, a hyphenated datestamp, optional "flag" letters "B" for breaking changes, "S" for security fixes, "F" for new features (more than one can be present, always in that order), and if more than one release is made on the same day the second & subsequent releases get a period followed by a release number starting from 1.
E.g. 3.2026-05-01FS for a first release of the day with a new feature & security fix to release series 3, 2.2026-05-01S backporting the security fix to release series 2 (LTS, say), 3.2026-05-02 for a "bug fixes & performance improvements" release, 3.2026-05-02S.1 for a security fix to the "bug fixes & performance improvements" release.
Like SemVer, this lets users know when there are breaking changes to an API or such, which releases contain security fixes, which releases have new features, and allows multiple simultaneous release versions for LTS support. Like CalVer it lets users know how recent a release is, and makes it pretty easy to figure out what the release schedule was historically (and likely still is) by comparing versions.
Are there any large projects that are older than 25 years that didn't go through that? The time based release thing as been recommended for about 25 years (can anyone get real data?), so newer projects may just be using it without knowing the pain, but for older projects it seems like they all went through it at least once.
Arguably Perl. And it also shows the real risk of sidelining everything for one big bang release.
I think they are just now back on to a regular release cadence, but they weren't for a long time.
IIRC, since GCC got covered by GPL3.
It used to be slower and I've spent way too much time working around C++ bugs in GCC 2.95
(The fact that I remember the problematic version is telling :)
Everybody remembers that specific version :). And I wasn't even programming professionally at that time!
For many years that was the only version that could be used. What become gcc3 took years. In the end it was better, but for a while gcc 2.95 was the best we had despite the bugs.
Oof...I know of at least 1 embedded shop still using 2.95.4 for various 'reasons'.
Ah the egcs drama.
They changed their major release numbers too tbf. 4.x it was point release per year, now it's a major release per year.
Yeah, GCC’s recent major releases have been remarkably regular, much like Fedora’s spring releases, and their releases seem to fit into the same broader rhythm. Hint? Red Hat.
It has been that way since people from Cygnus (now RedHat->IBM) reorganized the project
That is why egcs was launched, to get around the inability of the old team to do gcc releases. The issues had little to do with ideology and were about fixing a broken process and replacing it with something that had a hope of working.
I've already been using it for some time (debian sid has a trunk package). it has c++26 reflection, so I already do some magical things with reflection (much better for some cases e.g. for ser-des). I only wish they had a lsp server in their eco-system!
libstd has been giving me issues running gcc 16 binaries on Debian 12 and 13.
are you using
> The so-called "json" format for -fdiagnostics-format= has been removed in this release. Users seeking machine-readable diagnostics from GCC should use SARIF.
But:
> GCC can now output diagnostics in HTML form via -fdiagnostics-add-output=experimental-html
I wonder what drove the decision to remove JSON output and add HTML output?
It looks like SARIF is JSON, with a formal schema. I'm guessing the JSON they used to output used their own, non-standard schema.
Noob question - Does gcc use LLVM anywhere under the hood or it has its own code generation and optimization pipeline? How does it stack in comparison to LLVM?
Let me wikipedia that for you :-) Like people mentioned already, gcc predates LLVM by quite some years, ie. Wikipedia mentions March 22, 1987 for gcc, while LLVM's initial release was 2003.
A big difference between the 2 is also the license. GCC uses the GPL while LLVM uses Apache License, hence the projects don't share code.
> Does gcc use LLVM anywhere under the hood or it has its own code generation and optimization pipeline? No it doesn't use LLVM > How does it stack in comparison to LLVM? Well it support more targets then LLVM and in most cases it generates similar if not better executable.
The other answers (NO!) are correct, but... there was a gcc plug-in to use the llvm backend with gcc. Apple used llvm-gcc (circa 2012; gcc front end, llvm back end) while transitioning from gcc to llvm.
https://dragonegg.llvm.org
> Does gcc use LLVM anywhere under the hood
No
GCC far far far predates LLVM. They do not share code.
The libsanitizer library is imported from the LLVM sources into the GCC source tree. There are probably more examples of code sharing. Both projects are quite large.
I would assume the code sharing is one-way only due to the licenses. I.e. a GPL project can use MIT licensed code, but not vice versa (AFAIK).
Does -Ofast still ignore -fno-fast-math ?
I tried the unstable sources for a while, in the last ~3 months. I ran into some issues with some programs (could not compile them with recent GCC, but older GCC worked fine), so gcc 15.x works better for me in general (presently) - but from, say, +3000 programs to compile, the vast majority works well, and a few may need patches (which can often be found in LFS/BLFS by the way, they often use sed instructions to fix individual things and then it works usually).
Hopefully they fixed those issues. We all need stability and things-to-work.
Did you file any bug reports for them?