C++ Logo

std-proposals

Advanced search

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

From: Sebastian Wittmeier <wittmeier_at_[hidden]>
Date: Tue, 16 Jul 2024 23:42:15 +0200
Hi Marco,   It is not quite clear, what you are proposing. I know, unique_ptr is just one of the smart pointers. And you probably mean it more general ("by hardening smart pointers"), but how would you propose to do it:  - for the C++ standard you are proposing a new class std::safe_unique_ptr?  - you are propose a standard-relevant (breaking) behaviour change of std::unique_ptr relative to the status quo?  - you are proposing that the standard gives additional (non-breaking) guarantees for std::unique_ptr?  - you are recommending (important) implementation details of std::unique_ptr without any change to the standard? Then this list / a standard proposal is probably the wrong direction.   Just to check  - Is the cleanup stack multithreading-safe?  - Does it keep the order of destructions? All including the ones not using safe_unique_ptr?  - Does it prevent destructions from being delayed? E.g. the destruction is done, before delete returns and the destruction is done, when the scope is left, before any other instruction starts?     small typo in reference implementation? deleted() -> deleter();     Best, Sebastian   -----Ursprüngliche Nachricht----- Von:M.C.A. (Marco) Devillers via Std-Proposals <std-proposals_at_[hidden]> Gesendet:Di 16.07.2024 23:14 Betreff:[std-proposals] Save Cascading Free of Smart Pointers An:std-proposals_at_[hidden]; CC:M.C.A. (Marco) Devillers <marco.devillers_at_[hidden]>; 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; }; -- Std-Proposals mailing list Std-Proposals_at_[hidden] https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals  

Received on 2024-07-16 21:42:18