V725. Dangerous cast of 'this' to 'void*' type in the 'Base' class, as it is followed by a subsequent cast to 'Class' type.
The analyzer has detected a dangerous conversion of the "this" pointer to the "void*" type followed by a conversion of "void*" back to the class type. Casting "this" to "void*" is not in itself an error, but in certain cases the reverse conversion (from "void*" to the class pointer) is, which may be dangerous as the resulting pointer may appear incorrect.
The diagnostic description is pretty large and complex, but unfortunately I cannot help it. So please read it carefully to the end.
Let's discuss an example where "this" is cast to "void*" and after that the reverse conversion to the class type takes place:
class A
{
public:
A() : firstPart(1){}
void printFirstPart() { std::cout << firstPart << " "; }
private:
int firstPart;
};
class B
{
public:
B() : secondPart(2){}
void* GetAddr() const { return (void*)this; }
void printSecondPart() { std::cout << secondPart << " "; }
private:
int secondPart;
};
class C: public A, public B
{
public:
C() : A(), B(), thirdPart(3){}
void printThirdPart() { std::cout << thirdPart << " "; }
private:
int thirdPart;
};
void func()
{
C someObject;
someObject.printFirstPart();
someObject.printSecondPart();
someObject.printThirdPart();
void *pointerToObject = someObject.GetAddr();
....
auto pointerC = static_cast<C*>(pointerToObject);
pointerC->printFirstPart();
pointerC->printSecondPart();
pointerC->printThirdPart();
}
We would expect to get the following output:
1 2 3 1 2 3
But what will be actually printed is something like this:
1 2 3 2 3 -858993460
So, we get an incorrect output for all the data after the mentioned conversion sequence. The trouble is that the "pointerC" pointer is now pointing to the memory block allocated for object B, instead of the beginning of the C object as it did before.
This error may seem farfetched and unreal. But it is only obvious because the example above is short and simple. In real-life programs with complex class hierarchies, it may be far more confusing and vague. What makes this issue especially tricky is that when the "GetAddr()" function is stored in class A, everything works right, but if you store it in class B, then it doesn't. That may be quite embarrassing. So let's figure it all out.
To make it easier for you to understand the reason behind the error, we need to find out how objects of classes created through multiple inheritance are constructed and arranged in memory.
A schematic example is shown in Figure 1.
Figure 1 - Arrangement of an object of a class created through multiple inheritance in memory
As you can see from this figure, the object of class C (which is the one created through multiple inheritance) consists of the objects of classes A and B plus a part of object C.
Each of the "this" pointers contains the address of the beginning of the memory block allocated for the corresponding object. Figure 2 shows where "this" pointers point to for all the three objects.
Figure 2 - "this" pointers and memory blocks
Since the C-class object consists of three parts, its "this" pointer will be pointing not to the memory block added to the base classes, but to the beginning of the entire continuous memory block. That is, "this" pointers for classes A and C will coincide in this case.
The "this" pointer for the B-class object points to where the memory block allocated for it starts, but at the same time, the address of the beginning of this memory block is different from that of the memory block allocated for the C-class object.
So, when calling the "GetAddr()" method, we will get the address of object B and then, after casting the resulting pointer back to type "C*", we will get an incorrect pointer.
In other words, if the "GetAddr()" function were stored in class A, the program would work as expected. But when it is stored in B, we get an error.
To avoid errors like this, the programmer should carefully consider if they really need to cast "this" to "void*", and if the answer is certainly yes, then they must carefully check the inheritance hierarchy as well as any further reverse conversions from "void*" to the class pointer type.
References:
- Joost's Dev Blog. Hardcore C++: why "this" sometimes doesn't equal "this".
This diagnostic is classified as: