A common issue with C++ software is the need for a stable, standard ABI, which C provides but C++ does not (except through `extern "C"`). Because C++ name mangling is not standardized and the standard library headers have tons of implementation-specific details, something like `void foo(std::unique_ptr<Bar>);` does not have a stable ABI.
Also, I don’t think there is anything in the C, respectively C++ standards that forbids, respectively requires name mangling. Linking isn’t specified in either standard.
C++ implementations have name mangling because they live on systems designed for C, which don’t allow symbols such as int max(int,int)
C implementations (would) need name mangling if they ran on a system that used case-insensitive comparisons on symbol names, or on ones whose character set is smaller than that allowed in C symbols (I know examples of neither, but Wikipedia has an example that encodes the calling convention in the symbol name: https://en.wikipedia.org/wiki/Name_mangling#C)
The table in that article shows how different compilers mange very similarly but not the same! This is something that could be standardized for the benefit of all.
yet the real life answer is that I can link C++ code built with three different compiler on linux (gcc / clang / icc) and three / two on windows (clang / cl.exe / icc or clang / gcc)
For C: both platform have been EXTREMELY compatible over the years. They probably both treat breakage as bug. This is a must given the fundamental APIs are defined in term of C headers, and must remain compatible.
Even for C++: gcc has been mostly compatible, but not unreasonably so. MSVC was traditionally not at all, but has recently been for 2 or 3 major versions in a row. I think MSVC will eventually break it again though, but at least they break it way less often than before (and breaking can also have advantages).
In practice, there is a stable and standard ABI in C++. On Windows systems, you use the (undocumented, but mostly reverse-engineered) MSVC ABI. On everybody else, you use the confusingly-named Itanium ABI, except for exception handling on ARM, which uses a slightly different ABI.
Standard library implementations do not necessarily share the same ABI, but it is possible to interoperate between libc++ and libstdc++, and libstdc++ maintains ABI compatibility across different versions.
And, to be pedantic, C does not provide a stable, standard ABI. It is the platforms themselves that provide this ABI standard, and these platforms equally provide a C++ ABI standard. So C++ ABI compatibility is no more or less a problem than C ABI compatibility.
That's right. C being more limited, it has a more simplistic ABI. But the parent comment was replying the "need for a stable, standard ABI, which C provide". Which is misleading as both C++ and C have the same level of stability and standardisation of their ABI. (I.e: none of them is standardized, but both of them are stable on practice)
I'm really interested in the Core guidelines. They are only a few years old, however. It will be super interesting to see some case studies of large codebase adoption in the next few years. The lifetime profile is the one that I 'm most interested in seeing some real studies on. Some of the changes seem pretty simple (of course, I don't have to make them in any codebases) so it makes me wonder if they are expressive enough to solve a large chunk of the use after free problems.
I would prefer if C++ would be extended with a function level borrow checker with real safety guarantees that could optionally be turned on for functions.
In the first look Lifetime profile looks like it just tries to find the low hanging fruit cases.
Can it be further extended to guarantee safety guarantees in the future, or it's just a hack? I'm not sure about it.
RAII solves lots of lifetime problems, but it's surprisingly easy to have refs outlive their targets (among other things). Static-analysis can spot a portion of these, but in general the lifetime of input and output values of functions/scopes needs to be explicitly stated by the user (part of the type) and can thus be verified by the compiler.
I think Rust captures the core lifetime concepts pretty well, but the notation can be cumbersome, and I think C++'s rich type-system could make for a less-tedious solution. TFA mentions doing some implicit lifetime-analysis based on signatures (e.g. inferring ownership based on type-traits), but ultimately it is 'just' low-hanging fruit. It would be nice to capture in signatures/types that "the input must outlive the output" and have more meaningful error diagnostics and less implicit magic ("did I follow the iterator traits correctly?").
I think the main problem for C++ programmers is not the notation of Rust, as Rust is a simpler language than C++.
It's backwards compatibility: there are hundreds of millions, or probably billions of lines of C++ code out there. I found working with big code bases really hard, because the memory ownership situation is crazy, in fact impossible to keep track of.
That's why my question was whether there's a future path for C++ Lifetime Profile to get programs to Rust like memory safety in the future, but I still don't know the answer.
However that is also the reason why those billions of lines will stay C++, and no one will pay for porting them into Rust, or any other memory safe language.
So any improvement to the status quo is pretty much welcomed, and here lies the big difference between the C++ (introducing lifetime profile) and C communities (making Annex K optional), regarding taking actions to fix memory corruption.
I think the answer is yes. There's a complementary project [1] that aims to enforce a slightly more restricted memory safe subset of C++ than the lifetime profile checker does (or eventually will). The restrictions do not manifest as limitations on the code, but rather as extra run-time checks in some scenarios.
The C++ language maps one-to-one to the safe subset, so auto-conversion of (reasonable) existing C++ code to the safe subset should be a straightforward (if tedious) undertaking. The issue will be the performance of the converted code, which will depend on how pointers/references are used in the original code.
Pointers that are expected to never point to (in addition to never dereference to) a destroyed object can be converted to "safe" pointers with little run-time overhead [2]. Otherwise the pointer would need to converted to one with more overhead [3]. Pointers that can be verified (by the static analyzer) to conform to "scope lifetime" rules (akin to Rust) can remain zero-overhead pointers.
New code written in the safe subset would generally have performance in the ballpark of traditional C++ [4].
Unfortunately, documentation [1] is still kind of lacking. Like Rust (and arguably modern C++ conventions?), the safe subset doesn't support pointer arithmetic directly. You would have to convert the pointer to an iterator (and its target to an appropriate container).
Auto-conversion of code that uses pointer arithmetic is challenging, but has been demonstrated to be solvable in the general case [2].
> I know, C++ is a dirty snowball rolling downhill, gobbling up every feature it can put it's tendrils on, and always has been, but it is getting silly.
That's named evolving and improving. And that's how a language stay alive.
Yeah but it's hard to manage in multiple ways. Is there good tooling to help? And it means a lot of references you could normally check are using features you can't use.
Plus none of your libraries are going to agree on what parts to use.
Removing features from a programming language is almost impossible; it breaks backwards compatibility and so is a hard barrier to upgrading from the old version. Note the 12-year-and-counting struggle to adopt Python 3, or the fact that Perl 6 developers gave up completely trying to get people to upgrade from Perl 5 and just rebranded it as a new language instead.
C++ has opted to avoid those struggles by never removing features; you can disagree with that decision but it was done for good reasons.
I'm speaking strictly technical here, but there have been features that have been removed. Trigraphs (I think), and the register keyword. Moreover, there are objects that are very clearly marked deprecated, like auto_ptr. (Oh, and there is even talk of removing 'volatile' as a keyword.)
The register keyword issue crops up because legacy code included and linked to modern C++ will occasionally have the word "register" affixed to a loop variable. Generally speaking, once you change standards, there is room to drop the old stuff. It's just gradual. Static analyzers do a lot to modernize code now too, so the situation is not as painful as it used to be.
The `volatile` deprecation is more about providing new library features to do the things people (incorrectly) use `volatile` for today. `volatile` will still have legitimate use-cases.
On the other hand, JavaScript very successfully added strict mode that removed features like `eval`. The trick seems to be to ensure that "old code" and "new code" can live side-by-side during a transition period.
Vittorio Romeo has been proposing[0] an "epochs" feature for C++ which would enable modes like "strict." This would let them define things like const-by-default, requiring nulltpr instead of 0, etc. He's facing some resistance by other WG21 members.
> the fact that Perl 6 developers gave up completely trying to get people to upgrade from Perl 5 and just rebranded it as a new language instead
This statement does not do justice to the process that led to the renaming of "Perl 6" to "The Raku Programming Language". If you are interested in the process, https://github.com/Raku/problem-solving/issues/81 will give you some of the discussion that happened online.
C++ did remove features already a couple of times.
gets(), exception specifications, semantic corrections for a couple of cases like decltype vs auto, auto original meaning, std::bind(), and several others
However they don't play a Python 3, and rather take the effort to minimize the impact of those changes.
Even if you don't care about the language itself, there is a giant amount of C++ in the wild that won't be reimplemented. Anything that can make that code safer is a huge win.
I am working on a device, where the initial code was written in 1995-1997 on a pre cpp98 compiler, the code has recently been updated to cpp11, I am sure we will move to cpp14/17 soon. So this is slowly but steadily moving, even if very conservatively.
On the other hand, Hell would freeze over before you could convince the business to throw the cpp codebase and replace it by anything else, without necessarily a lot of things to show to the customer. And as much as we like to criticize business decisions, here it is probably the right call.
However, if you are startup starting today, it would probably be a good idea to consider other options, that could be used as an advantage against incumbents.
Because as much as I like Rust, it doesn't provide the IDE tooling for mixing language development with Java and .NET stacks, the seamless tooling that for creating COM/UWP components, something like Unreal or Unity for weekend coding hobbies, still builds slower than C++ because cargo doesn't support yet binary dependencies.
For writing memory safe code I already have .NET and JVM set of programming languages, so when I need to go outside of these platforms, I rather pick the tool that provides zero attrition with the existing tooling.
These are all issues that will be fixed, but they aren't yet there.
Here’s my problem with the approach: it’s a compiler warning. Nobody looks at those so it’s configured to be treated as an error. Now you have to deal with false positives and differences between implementations across compilers. I’d rather have this be part of the language with string guarantees and an obligation for compilers to adhere to the spec. Of course that isn’t possible in C++ without breaking existing code, which is why this ended up where it is.
Not a long term approach, in my humble opinion (as a C++ veteran that has worked with it for 20 years).
What the language needs is a new set of types that will ensure memory safety. Using the old types with the new ones should not allow the code to be compiled, thus ensuring memory safety.
It would be nice to have such a tool in short-term, though. At least some old bugs could possibly be more easily revealed.
55 comments
[ 4.2 ms ] story [ 123 ms ] threadC++ implementations have name mangling because they live on systems designed for C, which don’t allow symbols such as int max(int,int)
C implementations (would) need name mangling if they ran on a system that used case-insensitive comparisons on symbol names, or on ones whose character set is smaller than that allowed in C symbols (I know examples of neither, but Wikipedia has an example that encodes the calling convention in the symbol name: https://en.wikipedia.org/wiki/Name_mangling#C)
”If name mangling was standardized, could I link code compiled with compilers from different compiler vendors?
Short answer: Probably not.”
(click link for more info)
yet the real life answer is that I can link C++ code built with three different compiler on linux (gcc / clang / icc) and three / two on windows (clang / cl.exe / icc or clang / gcc)
That's not entirely true. C++ has a stable ABI: compiling a library with GCC and reusing it with clang or ICC is doable and commonly done nowadays.
The problem with the C++ ABI is that it is shamefully fragile. It is extremely easy to break an ABI without wanting it in C++, even for experts.
https://community.kde.org/Policies/Binary_Compatibility_Issu...
Neither the C Language Standard nor the C++ Language Standard specify an ABI.
Perhaps we should distinguish between a Langage-Standard ABI vs. a per-platform de facto ABI.
I think the GP's comment holds for the latter definition.
Even for C++: gcc has been mostly compatible, but not unreasonably so. MSVC was traditionally not at all, but has recently been for 2 or 3 major versions in a row. I think MSVC will eventually break it again though, but at least they break it way less often than before (and breaking can also have advantages).
Standard library implementations do not necessarily share the same ABI, but it is possible to interoperate between libc++ and libstdc++, and libstdc++ maintains ABI compatibility across different versions.
And, to be pedantic, C does not provide a stable, standard ABI. It is the platforms themselves that provide this ABI standard, and these platforms equally provide a C++ ABI standard. So C++ ABI compatibility is no more or less a problem than C ABI compatibility.
It's analogous to Java and Scala; each has an ABI, but it's easier to tell when the former breaks ABI than the latter.
There is a difference, but the difference is in complexity, not availability.
In the first look Lifetime profile looks like it just tries to find the low hanging fruit cases.
Can it be further extended to guarantee safety guarantees in the future, or it's just a hack? I'm not sure about it.
I think Rust captures the core lifetime concepts pretty well, but the notation can be cumbersome, and I think C++'s rich type-system could make for a less-tedious solution. TFA mentions doing some implicit lifetime-analysis based on signatures (e.g. inferring ownership based on type-traits), but ultimately it is 'just' low-hanging fruit. It would be nice to capture in signatures/types that "the input must outlive the output" and have more meaningful error diagnostics and less implicit magic ("did I follow the iterator traits correctly?").
It's backwards compatibility: there are hundreds of millions, or probably billions of lines of C++ code out there. I found working with big code bases really hard, because the memory ownership situation is crazy, in fact impossible to keep track of.
That's why my question was whether there's a future path for C++ Lifetime Profile to get programs to Rust like memory safety in the future, but I still don't know the answer.
So any improvement to the status quo is pretty much welcomed, and here lies the big difference between the C++ (introducing lifetime profile) and C communities (making Annex K optional), regarding taking actions to fix memory corruption.
The C++ language maps one-to-one to the safe subset, so auto-conversion of (reasonable) existing C++ code to the safe subset should be a straightforward (if tedious) undertaking. The issue will be the performance of the converted code, which will depend on how pointers/references are used in the original code.
Pointers that are expected to never point to (in addition to never dereference to) a destroyed object can be converted to "safe" pointers with little run-time overhead [2]. Otherwise the pointer would need to converted to one with more overhead [3]. Pointers that can be verified (by the static analyzer) to conform to "scope lifetime" rules (akin to Rust) can remain zero-overhead pointers.
New code written in the safe subset would generally have performance in the ballpark of traditional C++ [4].
[1] https://github.com/duneroadrunner/scpptool
[2] https://github.com/duneroadrunner/SaferCPlusPlus#norad-point...
[3] https://github.com/duneroadrunner/SaferCPlusPlus#registered-...
[4] https://github.com/duneroadrunner/SaferCPlusPlus-BenchmarksG... (note that the benchmark code is quite old and will be updated soon)
Auto-conversion of code that uses pointer arithmetic is challenging, but has been demonstrated to be solvable in the general case [2].
[1] https://github.com/duneroadrunner/SaferCPlusPlus#getting-sta...
[2] https://github.com/duneroadrunner/SaferCPlusPlus-AutoTransla...
I hope it gets enough backing from corporations that have big/huge C++ code bases.
That's exactly right.
> Can it be further extended to guarantee safety guarantees in the future, or it's just a hack? I'm not sure about it.
It could be extended to catch more cases, but it can't realistically be extended to create a useful C++ dialect that is really memory safe. See https://robert.ocallahan.org/2018/09/more-realistic-goals-fo....
I know, C++ is a dirty snowball rolling downhill, gobbling up every feature it can put its tendrils on, and always has been, but it is getting silly.
That's named evolving and improving. And that's how a language stay alive.
New code doesn't have to use old features.
Yeah but it's hard to manage in multiple ways. Is there good tooling to help? And it means a lot of references you could normally check are using features you can't use.
Plus none of your libraries are going to agree on what parts to use.
C++ has opted to avoid those struggles by never removing features; you can disagree with that decision but it was done for good reasons.
The register keyword issue crops up because legacy code included and linked to modern C++ will occasionally have the word "register" affixed to a loop variable. Generally speaking, once you change standards, there is room to drop the old stuff. It's just gradual. Static analyzers do a lot to modernize code now too, so the situation is not as painful as it used to be.
[0] http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p188...
This statement does not do justice to the process that led to the renaming of "Perl 6" to "The Raku Programming Language". If you are interested in the process, https://github.com/Raku/problem-solving/issues/81 will give you some of the discussion that happened online.
gets(), exception specifications, semantic corrections for a couple of cases like decltype vs auto, auto original meaning, std::bind(), and several others
However they don't play a Python 3, and rather take the effort to minimize the impact of those changes.
But the interop story between modern cpp and older cpp is much better than the interop story between rust and older cpp.
On the other hand, Hell would freeze over before you could convince the business to throw the cpp codebase and replace it by anything else, without necessarily a lot of things to show to the customer. And as much as we like to criticize business decisions, here it is probably the right call.
However, if you are startup starting today, it would probably be a good idea to consider other options, that could be used as an advantage against incumbents.
Lots of code might not be updated, thing is, with C++20 I still get to compile my pre-C++98 code.
Even taking into account the breaking changes between language revisions, they are quite minor versus writing into another language.
Plus there are still lots of stuff missing, like a proper GUI library that can even compete with MFC, OWL, let alone something like UWP or Qt.
And that will be still be able to interop with the "everything else" that doesn't get updated.
(Perhaps not seemlessly, but certainly better than a Rust <-> C++ interop.)
For writing memory safe code I already have .NET and JVM set of programming languages, so when I need to go outside of these platforms, I rather pick the tool that provides zero attrition with the existing tooling.
These are all issues that will be fixed, but they aren't yet there.
What the language needs is a new set of types that will ensure memory safety. Using the old types with the new ones should not allow the code to be compiled, thus ensuring memory safety.
It would be nice to have such a tool in short-term, though. At least some old bugs could possibly be more easily revealed.