On Tue, 16 Jul 2024, 18:51 M.C.A. (Marco) Devillers via Gcc, <
gcc@gcc.gnu.org> wrote:

> Document number:  SCF4C++00
> Date:  2024-7-16
> Audience:  GCC email list
> Reply-to:  marco.devill...@gmail.com, gcc@gcc.gnu.org
>
> I. 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
>

Where does it exist in the library?

The problem in your program is easily avoided, without changing the
standard or GCC's implementation of the standard library.

and also showcases a small trick by which that can be done.
>
> II. 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.
>

So don't do that then. It's easy to add a loop to clean up the list. Or
create an actual list class to manage the nodes and do that loop in its
destructor. Nobody is forced to define a list only in terms of nodes,
rather than a list class that manages the nodes.


> 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.
>
> 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;
> };
>

Reply via email to