Will this C++ code cause a memory leak (casting array new)

后端 未结 24 3052
暗喜
暗喜 2021-02-14 12:26

I have been working on some legacy C++ code that uses variable length structures (TAPI), where the structure size will depend on variable length strings. The structures are allo

24条回答
  •  逝去的感伤
    2021-02-14 12:49

    The various possible uses of the keywords new and delete seem to create a fair amount of confusion. There are always two stages to constructing dynamic objects in C++: the allocation of the raw memory and the construction of the new object in the allocated memory area. On the other side of the object lifetime there is the destruction of the object and the deallocation of the memory location where the object resided.

    Frequently these two steps are performed by a single C++ statement.

    MyObject* ObjPtr = new MyObject;
    
    //...
    
    delete MyObject;
    

    Instead of the above you can use the C++ raw memory allocation functions operator new and operator delete and explicit construction (via placement new) and destruction to perform the equivalent steps.

    void* MemoryPtr = ::operator new( sizeof(MyObject) );
    MyObject* ObjPtr = new (MemoryPtr) MyObject;
    
    // ...
    
    ObjPtr->~MyObject();
    ::operator delete( MemoryPtr );
    

    Notice how there is no casting involved, and only one type of object is constructed in the allocated memory area. Using something like new char[N] as a way to allocate raw memory is technically incorrect as, logically, char objects are created in the newly allocated memory. I don't know of any situation where it doesn't 'just work' but it blurs the distinction between raw memory allocation and object creation so I advise against it.

    In this particular case, there is no gain to be had by separating out the two steps of delete but you do need to manually control the initial allocation. The above code works in the 'everything working' scenario but it will leak the raw memory in the case where the constructor of MyObject throws an exception. While this could be caught and solved with an exception handler at the point of allocation it is probably neater to provide a custom operator new so that the complete construction can be handled by a placement new expression.

    class MyObject
    {
        void* operator new( std::size_t rqsize, std::size_t padding )
        {
            return ::operator new( rqsize + padding );
        }
    
        // Usual (non-placement) delete
        // We need to define this as our placement operator delete
        // function happens to have one of the allowed signatures for
        // a non-placement operator delete
        void operator delete( void* p )
        {
            ::operator delete( p );
        }
    
        // Placement operator delete
        void operator delete( void* p, std::size_t )
        {
            ::operator delete( p );
        }
    };
    

    There are a couple of subtle points here. We define a class placement new so that we can allocate enough memory for the class instance plus some user specifiable padding. Because we do this we need to provide a matching placement delete so that if the memory allocation succeeds but the construction fails, the allocated memory is automatically deallocated. Unfortunately, the signature for our placement delete matches one of the two allowed signatures for non-placement delete so we need to provide the other form of non-placement delete so that our real placement delete is treated as a placement delete. (We could have got around this by adding an extra dummy parameter to both our placement new and placement delete, but this would have required extra work at all the calling sites.)

    // Called in one step like so:
    MyObject* ObjectPtr = new (padding) MyObject;
    

    Using a single new expression we are now guaranteed that memory won't leak if any part of the new expression throws.

    At the other end of the object lifetime, because we defined operator delete (even if we hadn't, the memory for the object originally came from global operator new in any case), the following is the correct way to destroy the dynamically created object.

    delete ObjectPtr;
    

    Summary!

    1. Look no casts! operator new and operator delete deal with raw memory, placement new can construct objects in raw memory. An explicit cast from a void* to an object pointer is usually a sign of something logically wrong, even if it does 'just work'.

    2. We've completely ignored new[] and delete[]. These variable size objects will not work in arrays in any case.

    3. Placement new allows a new expression not to leak, the new expression still evaluates to a pointer to an object that needs destroying and memory that needs deallocating. Use of some type of smart pointer may help prevent other types of leak. On the plus side we've let a plain delete be the correct way to do this so most standard smart pointers will work.

提交回复
热议问题