Hacker News new | ask | show | jobs
by bad_user 4428 days ago
> If memory management is a serious problem for the software you work on, I've never found the boost library lacking.

As a developer that isn't working with C++, I'm finding memory management in C++ to be a nightmare and no amount of libraries can solve it.

Say you receive a pointer from somewhere. Is the referenced value allocated on the stack or on the heap? If allocated on the heap, do you need to free it yourself, or is it managed by whatever factory passed it to you? If you need to deallocate that value, is it safe doing so? Maybe another thread is using it right now. If you received it, but it should get deallocated by the factory that gave it to you, then how will the factory know that your copy is no longer in use? Maybe it's automatic, maybe you need to call some method, the only way to know is to read the docs or source-code very carefully for every third-party piece of code you interact with.

All of this is context that you have to keep in your head for everything you do. No matter how good you are, not matter how sane your practices are, it's easy to make accidental mistakes. I just reissued my SSL certificate, thanks to C++.

Yeah, for your own code you can use RAII, smart pointers, whatever is in boost these days and have consistent rules and policies for how allocation/deallocation happens. Yay! Still a nightmare.

Even if manageable, there's a general rule of thumb that if a C++ project doesn't have multiple conflicting ways of dealing with memory management and multiple String classes, then it's not mature enough.

4 comments

> Say you receive a pointer from somewhere.

Here's your problem. In general I don't want to be receiving a single pointer from anyone. Lately, I've found it helpful to think of pointers in C++ as special iterators rather than a referential relic from C. In such a mindset passing pointers around without an accompanying end iterator, or iteration count, just makes no sense. Anywhere that implied iteration count is always a constant, I'm probably not structuring my code correctly.

So my recommendation is to use references (foo&) for passing down (well, up) the stack, never to heap allocated objects. Because you can't use delete on a reference there's no longer an ambiguity. Use smart pointers to manage the heap. Write RAII wrappers (it's not a lot of code) to manage external resources. RAII wrappers are especially useful for encapsulating smart pointers so big things can be passed around with value semantics, which gives you even stronger ability to reason. Implementing optimisations like copy-on-write becomes fairly trivial.

> I just reissued my SSL certificate, thanks to C++.

If you're referring to Heartbleed then OpenSSL is written in C, not C++. Generally only a language that inserts array bounds checks for every access would have shielded you from this bug... C++s <vector> does this if you use the at() function of <vector>, but op[] doesn't by default for performance reasons.

The problem that Rust solves is that your advice, while good, is still advice. I absolutely agree that naked pointers are a code smell, and stack allocated objects should be the norm, with passing around (const) references to them. And RAII wrappers are great.

But all of that are patterns of use, enforced mostly by convention. In Rust, that's enforced by the language itself, and violating it will be a compiler error. The following kind of shenanigans won't be allowed outside of unsafe regions:

  int main()
  {
    int on_stack;
    int& ref = on_stack;
    int* ptr = static_cast<int*>(&ref);
    delete ptr;
    return 0;
  }
Yes, it's obviously bad code, but C++ happily let me write it, and it compiled with no warnings under -Wall -Wpedantic.
This is because delete is an operator that can be overridden, and whether it has been overridden isn't known until link time.

    void operator delete(void*) {  }

    int main()
    {
      int on_stack;
      int& ref = on_stack;
      int* ptr = &ref;
      delete ptr;
      return 0;
    }
and now it's safe :P... and yes, never freeing any memory is arguably a perfectly valid memory management strategy. Ok, this example is nuts... but it's a feature of C++, in the C tradition, that it lets you do crazy things. Can I plug custom per-type memory allocators in to Rust?
> In such a mindset passing pointers around without an accompanying end iterator, or iteration count, just makes no sense. Anywhere that implied iteration count is always a constant, I'm probably not structuring my code correctly.

Passing around two iterators is still not safe, due to iterator invalidation.

Memory safety in the C++ model is a hard problem.

> So my recommendation is to use references (foo&) for passing down (well, up) the stack, never to heap allocated objects.

I don't think this is practical. Consider `operator[]` on a vector, which returns a reference to a heap allocated object. If that were to copy out, you'd have a lot of overhead, and if it were to move, then a lot of very common patterns would be annoying to write.

As a developer that isn't working with C++, I'm finding memory management in C++ to be a nightmare

?

I've never had an issue with memory management in C / C++ (and I've had experience of writing C#/Java in enterprise applications, so I know what not having to do it is like).

There is an overhead to having to think and plan for it, but in my experience of using it for things ranging from realtime systems through to high performance, multi-threaded image processing and rendering applications, at least if you're in control of most of the code and it's pretty good code, it's not really an issue.

Of course, if it's good code, then you're good. The issue is getting good code in the first place. ;) That's where static analysis can help: compilers are tireless. Humans are faulty.

Memory management issues cause tons of security issues in C++ applications. In the recent Pwn2Own, all of the security vulnerabilities in Firefox would have not been possible had Firefox been implemented in Rust.

> if a C++ project doesn't have multiple conflicting ways of dealing with memory management and multiple String classes, then it's not mature enough.

Hmm… How can I tell maturity from rot?

bad_user's definition of maturity sounds an awful lot like rot to me...
That was me being sarcastic. Taking it seriously implies that you felt it to some degree, no? :-P
Sigh. And here I thought I had my sarcasm detector properly calibrated...