- cross-posted to:
- hackernews@derp.foo
- cross-posted to:
- hackernews@derp.foo
This blog post writes a dissertation about garbage collection, heap memory management, the absolute need to take courses on assembly language, and other contrived and absurd tangents.
Looking at the code, the guy gets a double-free because he instantiates two
std::unique_ptr
from the same raw pointer.I’m sure the author felt very clever to pull up all these topics to write a blog post about, but in the end all they’re doing is writing buggy code based on their misconception of a topic.
Yeah, unless I’m missing something the author would have the same outcome with regular pointers if he’d freed them at the same time (one at the end of the anon scope and one at the end of fun1). This is nothing to do with garbage collection and is simply a result of, as you mention, pointing to the same memory with two pointers and freeing both.
His issue seems to be with the implementation of malloc, which is pretty funny because he’s basically claiming C itself has unpredictable garbage collection. I almost can’t believe it’s legit, it seems like such a basic, fundamental misunderstanding of concepts it’s like chatGPT output.
Yeah, unless I’m missing something the author would have the same outcome with regular pointers if he’d freed them at the same time (one at the end of the anon scope and one at the end of fun1).
That’s basically it. The way the blogger wrote
fun1
means the pointer is freed once the function exits, because they explicitly added thestd::unique_ptr
to take over its lifetime. Afterwards they are surprised by the fact that it really took over its lifetime.The weird part is that the blogger had to go way out of its way to write that bug.
I almost can’t believe it’s legit, it seems like such a basic, fundamental misunderstanding of concepts it’s like chatGPT output.
I agree. It almost sounds like one of those coding exercises recruiters throw candidates in preliminary hiring rounds to weed out the bottom 5% that have no idea what they are doing.
You can call it writing buggy code based on misconceptions, but the fact that it’s possible (and not even especially difficult) to misuse smart pointers badly enough to produce program crashes and undefined behavior is still a fundamental weakness of C++ as a language.
As a counterexample, this type of bug is impossible to produce in Rust without explicitly using the
unsafe
keyword, and that keyword is something that is almost never used by regular developers and is an easy thing to audit for.Edit: That being said, if you’re stuck using C++ then obviously using smart pointers is the right thing to do whereas using raw pointers and managing the memory yourself is completely asinine, so if the author’s point is to not use smart pointers in C++ then I suppose they want you to just… Leak memory? Because if you’re able to figure out where it’s safe to free a raw pointer, then you’re able to figure out how to correctly use a smart pointer in that situation.
the fact that it’s possible (and not even especially difficult) to misuse smart pointers
Any tool can be misused, but there’s a saying about those who blame the tools.
If you use a component designed to take over the ownership of an object but somehow make the mistake of assigning the same object to multiple components, the problem is not the language you’re using. The problem is that you aren’t paying attention to what you’re doing.
That’s awfully reductive.
Tools can absolutely vary in their qualities and in their risks / benefits. I don’t know what kind of engineer wouldn’t evaluate their choice of tools before using them. If you have a tool that explodes in your face when it gets jostled, that’s a badly designed tool.
If you have no other choice for the work you need to do, then okay… get very good at using the dangerous tool. But if an alternative tool exists that is not only safer but also more efficient, easier to use, and more productive in every use case then the biggest problem really is the choice of tool.
That’s awfully reductive.
It really isn’t. Otherwise there would be programming languages out there that would make it impossible to write buggy code, and there is nothing of the sort.
Tools can absolutely vary in their qualities and in their risks / benefits.
You still get bugs. This isn’t up for discussion. In fact, the only difference is that somehow you assert that C++ suffers from this issue but started to backpedal when any language other than C++ is brought into the picture. That hardly sounds like a personal assertion that’s grounded and well founded.
What exactly have I backpedaled on in any of my replies?
Rust.
Rust eliminates entire categories of bugs at compile time with performance that is on par with C++ and often better.
I do get bugs in my Rust code, but do you want to know what they are? Once in a while I forget to type a
!
in an if-statement. Or I accidentally type&&
when I meant to type||
. These mistakes are trivially caught in unit tests or with a single run of the application and easily fixed. It’s also very rare for me to actually make these mistakes. Almost every single time I compile my Rust code, everything works on the first try. But I confess, once in a while one of these minor bugs slips in there.So yes bugs are possible in every language. But there’s a lot to be said about what kinds of bugs are possible, what the risks of those bugs are, and what the process of mitigating them is like. A memory corruption bug is an entirely different beast from a simple Boolean logic bug.
It’s maybe a weakness if you dont know what you’re doing, the strength and power of C (and thus if you wish C++) lies in just that you can manipulate memory directly with just a raw pointer however dangerous it is.
It’s maybe a weakness if you dont know what you’re doing
The experience of the last 30 years or so of critical security holes across all types of organisations and individuals due to memory bugs seems to indicate that no one knows what they are doing
Yeah can’t argue with that 😁
This statement is incredibly naive. I know exactly what I’m doing with C++ and yet it’s still incredibly easy to introduce subtle bugs as soon as you’re developing anything moderately complex, especially when async or threading are involved. It’s not that I’m unaware of what problems may happen, it’s that the language expresses itself poorly and a subtle typo or innocent assumption can lead to catastrophic failures that might only surface in 0.0001% of the times that a certain line of code is run.
I used to consider C++ my favorite language by an enormous margin because I assumed that we just have to accept the danger to get the benefit of performance. I used to eagerly follow developments coming from the C++ ISO committee, excited for the language improvements that would be coming down the line.
Then I started playing with Rust and learned what it’s like to get incredible performance without being mired in hazards. Imagine outperforming C++ on average while also having your code almost always work perfectly the first time you compile it and never crash or segfault. That’s Rust, and once you give it a fair try, you’ll never want to go back to C++.
Being able to manipulate raw pointers with no guardrails ever is not a strength, it’s a terrible weakness, and you’ll live a happier life the sooner you realize that. Consider instead if you could have all the same level of control over memory except the compiler tells you whenever you’re doing something asinine before you run it. You can always force it to do what you’re asking for by declaring it
unsafe
but you’ll quickly realize that you never actually want that.Well then you’re better off with Rust! I have heard the same thing about Java, C# and even scripting languages like Python. It suits you and the project? Go for it!
But the raw pointer in C (and thus C++) is the absolute power, and that’s about it. That is how computers are built, that is how compilers work, that’s how about anything works under the hood. Like it or not.
You dont have to like it, I understand that completely.
Absolute power doesn’t have to come with unnecessary risk everywhere. There’s nothing you can do in C++ that can’t also be done in Rust, the difference is that Rust keeps it safe by default and you need to go out of your way to say “Let me do this incredibly risky thing inside this specific block of code” before it lets you do anything that has any risk of leading to undefined behavior and crashes.
And it turns out, you can have all the performance advantages of C++ while almost never needing to do anything unsafe. The average Rust developer will never use the
unsafe
keyword in their entire career but will still write code that outperforms the average C++ developer.Yeah, I have been a long for some time and that was the speech for Java, C# etc too you know.
Real like isn’t usually coding a new program with unsafe pointers for speed, it’s usually a 20 y old codebase in C/C++ that interferes with third parties in C, C++, C# and two distinct versions of Java, incompatible between each other.
So maybe Rust is the new deal, like Python was supposed to be and Java before that, we’ll see…
Python never claimed to be the new deal, neither did java. At least not in the “c++ is now obsolete” way.
Rust definitely can replace c++, the major reason for not doing so right now is the legacy stuff, but as rust / c++ interop improves I think we will see more companies moving away from c++.
and a subtle typo
What example can you give to illustrate that claim?
or innocent assumption
That sounds like an euphemism for “I don’t know what I’m doing, so I pin my mistakes on my tools”.
can lead to catastrophic failures that might only surface in 0.0001% of the times that a certain line of code is run.
I struggle to tell if you are serious.
What example can you give to illustrate that claim?
[& var1, var2](){ /* ... */ }
when you meant to type
[&, var1, var2](){ /* ... */ }
can lead to data races in multithreaded contexts, undefined behavior, and sporadic crashing.
I struggle to tell if you are serious.
I think you’ve never written multithreaded or async C++ code. Or anything particularly complex if you’re unfamiliar with the issues that I’m bringing up.
I agree with what you’re saying even though I do think a lot of C++'s bad rep comes either from C or from pre-C++11 code. I also think that modern code should include clang-tidy in the CI, and if so at least simple mistakes like in OPs code would be flagged with “warning: Use of memory after it is freed [clang-analyzer-cplusplus.NewDelete]”
https://clang-tidy.godbolt.org/z/8E169bons
Note that all of the warnings in there are valid and should be fixed, so it’s not like wading through a see of false positives. That being said, the post is interesting in its explanation of why the example does what it does. Too bad all of the other stuff in there is bonkers.
Linters are good and should absolutely be used in any serious C++ project, but they can only catch the most basic sources of UB. I almost never make a mistake that a static analyzer can catch. It’s the multithreaded lifetime issues and data races that ambush you the hardest, and I don’t see any way a C++ static analyzer could hope to catch those.
But yes, most of the original post is bonkers and has the totally wrong conclusion.
the fact that it’s possible (and not even especially difficult) to misuse smart pointers badly enough to produce program crashes and undefined behavior is still a fundamental weakness of C++ as a language
I would argue it’s the natural state of C++. Crashes and UB lurk in every shadow, ready to pounce upon the unwary programmer.
It’s a powerful tool, but it’s not very forgiving.
I agree that it’s the natural state of C++, and my point is that this is makes C++ not a good choice when an alternative that has all the strengths of C++ and none of the weaknesses exists.
I’ve been using unique_ptr since way before it was called that, and it’s not something magical. You need to know what it does and what it doesn’t or you’ll be surprised.
I always thought of unique as a warning not a guarantee. Make sure it’s the only thing pointing at your structure, or you’ll be in trouble.
You need to know what it does and what it doesn’t or you’ll be surprised.
I don’t think that
std::unique_ptr
is shrouded in mystery: it’s designed to be the unique handle of a raw pointer, and it frees the memory when it’s lifetime ends. As it’s designed to be the unique holder of a resource, it’s implemented to disallow making copies. It might be implemented in clever ways, but a developer experience point of view it’s quite straight to the point.
Wait until this guy discovers you can cast away const! Or bypass private/protected with casting.
“They say C++ is specified in an international standard, but there is behavior that’s left undefined. Are they lazy, or did their printer ran out of ink?”
It’s a bad day to know how to read