C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Save Cascading Free of Smart Pointers

From: Jens Maurer <jens.maurer_at_[hidden]>
Date: Tue, 16 Jul 2024 23:30:48 +0200
Why is your proposal limited to unique_ptr and does not also
cover shared_ptr or other smart pointers?

Can you write your sample code using the deleter customization
of unique_ptr?

Can you show any actual examples of
"can core dump easily and unexpectedly. For example: GUIs, editors,
parsers, transformers, etc."
?

I haven't seen any, and I don't expect people to write linked lists
with unique_ptr, either. One reason not to do that is that we want
allocator support in general, and storing the allocator once (for
the entire data structure) is much cheaper than storing it with
every list node.

Jens


On 16/07/2024 23.13, M.C.A. (Marco) Devillers via Std-Proposals wrote:
> I came up against this and wanted to float an idea. It doesn't seem
> popular but I think I am right. This should be resolved.
>
> Document number: xxx
> Date: 2024-7-16
> Audience: GCC email list
> Reply-to: marco.devillers_at_[hidden], std-proposals_at_[hidden]
>
> I.
> II. Introduction
>
> Because C++ smart pointers are based on RAII it is easy to trigger an
> overflow of the C stack since destructors call each other. Smart
> pointers are supposed to be safe, smart pointers are likely to be used
> extensively in the future, and this behaviour could make a large
> number of C++ programs core dump unexpectedly.
> This proposal is to remove this behaviour from GCCs standard library
> and also showcases a small trick by which that can be done.
>
> III. Motivation and Scope
>
> We all want smart pointers since they allow for easy and safe memory
> management, this desire is only expected to increase over the
> following decades.
>
> However due to RAII semantics it's easy to trigger an overflow of the
> C stack once garbage goes out of scope. Observe the following trivial
> program:
>
> #include <iostream>
> #include <memory>
>
> struct list_node {
> using ptr = std::unique_ptr<list_node>;
> ~list_node() {
> }
>
> int x;
> ptr next;
> };
>
> int main() {
> list_node::ptr next = nullptr;
>
> for(int i = 0; i < 100000; ++i) { // decrease value to see it not segfault
> next = list_node::ptr(new list_node{i, std::move(next)});
> }
> }
>
> Cascading frees will make this program core dump depending the size of
> the list. Please note, that that program will segfault on for current
> data-driven days relatively tiny sizes.
>
> I give it to you that this is unsafe and unwanted behaviour since now
> every program employing nested structures can core dump easily and
> unexpectedly. For example: GUIs, editors, parsers, transformers, etc.
> The problem is only expected to worsen with more developers using safe
> pointers.
>
> The proposal is to remove this behaviour from the GCC standard library
> by hardening smart pointers in the following manner: instead of
> calling garbage recursively garbage is first offloaded to a stack and
> that stack destroys objects until it is empty. Or by any other means
> that removes segfaulting cascading frees.
>
> A reference implementation (not concurrent) for unique pointers is
> given as an Addendum. (The code is due to Alipha on libera.net).
>
> IV. Impact On the Standard
>
> This shouldn't impact other parts of the standard.
>
> V. Design Decisions
>
> Offload destructor calls to an explicit stack to make these calls
> sequential instead of recursive. A non-concurrent implementation of
> unique pointers is given below.
>
> VI. Technical Specifications
>
> This shouldn't change anything.
>
> VII. Acknowledgements
>
> This cascading free behaviour was noticed during the development of
> the Egel language interpreter, and the author has great interest in
> having this resolved. The problem was discussed on various channels
> and together with Alipha on libera.net a solution was developed. The
> idea was subsequently floated on the GCC mailing list where it was
> forwarded.
>
> VIII. Addendum
>
> #include <iostream>
> #include <functional>
> #include <vector>
> #include <utility>
> #include <memory>
>
> namespace detail {
> inline bool doing_cleanup = false;
> inline std::vector<std::function<void()>> ptr_cleanup;
> }
>
> template<typename T>
> class safe_unique_ptr {
> public:
> safe_unique_ptr() : ptr() {}
> safe_unique_ptr(T *p) : ptr(p) {}
>
> safe_unique_ptr(safe_unique_ptr &&other) :
> ptr(std::exchange(other.ptr, nullptr)) {}
>
> safe_unique_ptr &operator=(safe_unique_ptr &&other) {
> cleanup(ptr);
> ptr = std::exchange(other.ptr, nullptr);
> return *this;
> }
>
> T &operator*() const { return *ptr; }
> T *operator->() const { return ptr; }
>
> ~safe_unique_ptr() { cleanup(ptr); }
>
> private:
> void cleanup(T *p) {
> using namespace detail;
>
> if(!p) return;
>
> if(!doing_cleanup) {
> doing_cleanup = true;
> delete p;
>
> while(!ptr_cleanup.empty()) {
> std::function<void()> deleter = ptr_cleanup.back();
> ptr_cleanup.pop_back();
> deleted();
> }
>
> doing_cleanup = false;
> } else {
> ptr_cleanup.push_back([p]() { delete p; });
> }
> }
>
> T *ptr;
> };
>
> struct list_node {
> using ptr = safe_unique_ptr<list_node>;
> ~list_node() {}
>
> int x;
> ptr next;
> };

Received on 2024-07-16 21:30:52