Tuesday, September 11, 2018

[C++] What warning C4291 is and how to deal with it

After Java, C++ impresses me with his manual memory management and the idea of how many surprises a developer may meet on his/her way. For example, if you define your own operator new with custom arguments (it is called "class-specific placement allocation functions"), a placement form of operator delete that matches the placement form of operator new has to be defined (the corresponding form of the delete operator must have the signature void operator delete(void *ptr, user-defined-args...)). When an exception is thrown in a class constructor the placement form of operator delete will be invoked and take care of the memory reclaimation. If there is no the placement form of the operator, no one will be able to take care of the memory and a memory leak occurs.

Warning C4291


Fortunately, the MSVC compiler lets us know about the dangerous using warning C4291 no matching operator delete found; memory will not be freed if initialization throws an exception (please, compile the code with /EHsc /W1). Let's have a look:

[1/2] Building CXX object src\memory\CMakeFiles\placement-new-delete.dir\PlacementNewDelete.cpp.obj
..\src\memory\PlacementNewDelete.cpp(67): warning C4291: 'void *MyClassA::operator new(size_t,MyAllocator &)': no matching operator delete found; memory will not be freed if initialization throws an exception
..\src\memory\PlacementNewDelete.cpp(32): note: see declaration of 'MyClassA::operator new'
[2/2] Linking CXX executable src\memory\placement-new-delete.exe

The warning is described in detail there: Compiler Warning (level 1) C4291 (MSDN).

The problem you can be faced is the following: in some cases, it's not so easy to implement the placement form of operator delete. Operator new forewer takes the size argument - the ammount of memory required for the object while operator delete doesn't but the argument may be necessary to allocate the memory and return it to the operating system then.

Writing your own allocator


Let's consider the following pattern: a third-party memory allocator is used to allocate memory for objects, the allocator has two methods: allocate and deallocate and each method takes a size_t size parameter. I see this code very often through the Eclipse OMR JIT compiler, so I believe the pattern is quite popular.




An example how to use the allocator:



The second operator delete is needed to allow destroying objects with the standard delete ptr; construction. The code has no errors: some amount of bytes are allocated in operator MyClassA::new and if an exception occurs in the constructor, operator delete will takes care of the memory. The amount of memory, which should be allocated and reclaimed, is equals to the result of the sizeof(MyClassA) expression. We can check it looking at the output of the program bellow:

MyClassA::operator new(size_t size, MyAllocator& allocator)
size: 24 sizeof(MyClassA): 24
MyClassA::operator delete(void *ptr, MyAllocator& allocator)
sizeof(MyClassA): 24

But what about subclasses of the class MyClassA? If we have a class MyClassB and (1) the class is derived from class MyClassA, (2) we as developers want to get all advantages from the polymorphism property and use objects of the class MyClassB by pointers to objects of the basic class MyClassA:



The program returns the following:

MyClassA::operator new(size_t size, MyAllocator& allocator)
size: 32 sizeof(MyClassA): 24
MyClassA::operator delete(void *ptr, MyAllocator& allocator)
sizeof(MyClassA): 24

Something wrong? An object of the MyClassB takes 32 bytes (exactly this value is passed as the first parameter of the operator new) but the expression sizeof(MyClassA) returns a less value: 24. The code above doesn't work as designed and a memory leak is here: the delete operator reclaimes less amount of memory than it was allocated for the object.

Overriding of the delete operator in derived classes


The first idea that comes to the mind is the following: to override the delete operator in the MyClassB class in order to use the sizeof expression:

allocator.deallocate(ptr, sizeof(MyClassB));

The solution is very simple but scales badly: the operators operator delete(void *ptr, MyAllocator& allocator) and operator delete(void *ptr, size_t size) must be overriden in every class derived from MyClassA and such classes may be a lot (for example I have counted 33 classes derived from TR::Optimizer in the catalog compiler/optimizer inside the Eclipse OMR project codebase, yes, modern compilers provide many optimizations). Because each derived class must contain two operators which different from each other only by the sizeof(MyClassXXX) parameter, there is a great field for the copy/paste 'solution'. One day the developer will just forget to change MyClassXXX to MyClassYYY or, even worse, to override the operators (and it is very dangerous when a new developer join the team). I believe it is much better to save the size of allocated memory somewhere...

Saving the object size in the allocated memory block


Since operators new and delete are actual static (even though their declaration rule allows skipping the static keyword), the code of the operators cannot get access to fields of the constructed object (and this is logically, when the new operator is running the object doesn't exist). The question arose where we can store the size of the constructed object if we want to use it in the delete operator? The solution may be the following: to allocate a bit more memory in the new operator, write down the size of the allocated object at the beginning of this additional memory block and return an address of the memory just after the written down object size:

0xAA...00 - the object size (8 bytes on 64-bits architectures), 0xAA...08 the pointer to return, object data.

The delete operator works as a mirror: the operator takes the ptr parameter - the pointer to the object data. We should substruct the amount of memory reserved for the object size (size_of(size_t)) so we will get the beginning of the allocated memory block and this beginning should be passed to the used memory reclamation procedure. The size of reclaimed memory is increased by the amount of memory required to store the object size.

The overriden delete operators can be removed from MyClassB.

The following code demonstrates the idea (the getI() method is added to demonstrate the location of the object, the line thow "Fail"; in the MyClassA constructor must be commented to let the method work).



If an exception occurs in the constructor of the MyClassA class:

MyClassA::operator new(size_t size, MyAllocator& allocator)
size: 32 allocated: 40
MyClassA::operator delete(void *ptr, MyAllocator& allocator)
size: 32 allocated: 40

The size of an object of MyClassB (sizeof(MyClassB)) is equal to 32 bytes (see the output of the program from the above paragraph) but 40 bytes will be allocated because 8 bytes is reserved to store the object size.

Would you like to give a 'Like'? Please follow me on Twitter!

No comments:

Post a Comment