Search code examples
c++mallocinterruptnew-operatordelete-operator

Safe/good way to wrap c++ new/delete for multi-threaded environment


I was having some issues with new/deletes occurring on different threads (TI F28379D) that were causing the program to lock if an interrupt occurred in the middle of allocating or deleting. I didn't want to wrap every instance of new/delete with a macro that disables and enables interrupts, as it felt like a lot of duplicate code. My solution was to create two functions that have the appropriate macros around the new/delete.

void *create_array_pointer(uint16_t count, size_t size, char *variable_name) {
    //create the array (inside where interrupts are disabled
    uint16_t interrupt_settings = __disable_interrupts();
    void *ptr = new uint16_t[count*size];
    __restore_interrupts(interrupt_settings);
    check_allocation_throw_error(ptr, variable_name);
    return ptr;
}

void delete_array_pointer(void *ptr) {
    uint16_t interrupt_settings = __disable_interrupts();
    delete[] ptr;
    ptr = NULL;
    __restore_interrupts(interrupt_settings);
}

This works for primitive types. However, I realized today that this is a bad solution for c++ classes that have default constructors/destructors, as the constructor or destructor will never be called automatically.

Is there some way to make sure the constructors/destructors are called, without going back to wrapping every call?


Solution

  • I am assuming you are in an environment where you can't use containers or smart pointers. Otherwise I would recommend returning std::vector or std::unique_ptr.

    You can use templates to retain the type of the objects you are creating whilst still being generic.

    Also you may want to consider a RAII way to manage your interrupts as they will be exception safe as well as being generally safer and easier:

    template<typename T>
    void check_allocation_throw_error(T*, char const*) {}
    
    // This will disable interrupts on creation and re-enable them
    // when it goes out of cope. This means if you reach the end
    // of the function OR if an exception is thrown the interrupts
    // will be re-enabled regardless.
    class interrupt_disabler
    {
    public:
        interrupt_disabler(): settings(__disable_interrupts()) {}
        ~interrupt_disabler() { __restore_interrupts(settings); }
    
    private:
        interrupt_disabler(interrupt_disabler const&) {}
        interrupt_disabler& operator=(interrupt_disabler const&) { return *this; }
    
        uint16_t settings;
    };
    
    
    template<typename T>
    T* create_array_of(uint16_t count, char const* variable_name)
    {
        T* ptr = 0;
    
        // create a scope where interrupts are disabled
        {
            interrupt_disabler lock(); // enabled at end of scope OR if new throws
            ptr = new T[count];
        }
        // interrupts enabled automatically here
    
        check_allocation_throw_error(ptr, variable_name);
    
        return ptr;
    }
    
    template<typename T>
    void delete_array(T* ptr)
    {
        interrupt_disabler lock(); // enabled at end of scope
        delete[] ptr;
    }
    
    struct MyType {}; // any old type
    
    int main()
    {
        MyType* mytype = create_array_of<MyType>(10, "wibble");
    
        delete_array(mytype);
    }