Pointer values are different but they compare equal. Why?

C++PointersMultiple Inheritance

C++ Problem Overview


A short example outputs a weird result!

#include <iostream>

using namespace std;

struct A { int a; };    
struct B { int b; };
struct C : A, B
{
	int c;
};

int main()
{
	C* c = new C;
	B* b = c;

	cout << "The address of b is 0x" << hex << b << endl;
	cout << "The address of c is 0x" << hex << c << endl;

	if (b == c)
	{
		cout << "b is equal to c" << endl;
	}
	else
	{
		cout << "b is not equal to c" << endl;
	}
}

It's very surprising to me that the output should be as follows:

The address of b is 0x003E9A9C
The address of c is 0x003E9A98
b is equal to c

What makes me wonder is:

0x003E9A9C is not equal to 0x003E9A98, but the output is "b is equal to c"

C++ Solutions


Solution 1 - C++

A C object contains two sub-objects, of types A and B. Obviously, these must have different addresses since two separate objects can't have the same address; so at most one of these can have the same address as the C object. That is why printing the pointers gives different values.

Comparing the pointers doesn't simply compare their numeric values. Only pointers of the same type can be compared, so first one must be converted to match the other. In this case, c is converted to B*. This is exactly the same conversion used to initialise b in the first place: it adjusts the pointer value so that it points to the B sub-object rather than the C object, and the two pointers now compare equal.

Solution 2 - C++

The memory layout of an object of type C will look something like this:

|   <---- C ---->   |
|-A: a-|-B: b-|- c -|
0      4      8     12

I added the offset in bytes from the Address of the object (in a platform like yours with sizeof(int) = 4).

In your main, you have two pointers, I'll rename them to pb and pc for clarity. pc points to the start of the whole C object, while pb points to the start of the B subobject:

   |   <---- C ---->   |
   |-A: a-|-B: b-|- c -|
   0      4      8     12
pc-^   pb-^

This is the reason why their values are different. 3E9A98+4 is 3E9A9C, in hex.

If you now compare those two pointers, the compiler will see a comparison between a B* and a C*, which are different types. So it has to apply an implicit conversion, if there is one. pb cannot be converted into a C*, but the other way round is possible - it converts pc into a B*. That conversion will give a pointer that points to the B subobject of wherever pc points to - it is the same implicit conversion used when you defined B* pb = pc;. The result is equal to pb, obviously:

   |   <---- C ---->   |
   |-A: a-|-B: b-|- c -|
   0      4      8     12
pc-^   pb-^
   (B*)pc-^

So when comparing the two pointers, the compiler in fact compares the converted pointers, which are equal.

Solution 3 - C++

I know there is an answer but maybe this will be more straightforward and backed-up by an example.

There is an implicit conversion from C* to B* on c operand in here if (b == c)

If you go with this code:

#include <iostream>

using namespace std;

struct A { int a; };    
struct B { int b; };
struct C : A, B
{
    int c;
};

int main()
{
    C* c = new C;
    B* b = c;

    cout << "The address of b is 0x" << hex << b << endl;
    cout << "The address of c is 0x" << hex << c << endl;
    cout << "The address of (B*)c is 0x" << hex << (B*)c << endl;

    if (b == c)
    {
        cout << "b is equal to c" << endl;
    }
    else
    {
        cout << "b is not equal to c" << endl;
    }
}

You get:

The address of b is 0x0x88f900c
The address of c is 0x0x88f9008
The address of (B*)c is 0x0x88f900c
b is equal to c

So c casted to B* type has the same address as b. As expected.

Solution 4 - C++

If I may add to Mike's excellent answer, if you cast them as void* then you will get your expected behaviour:

if ((void*)(b) == (void*)(c))
    ^^^^^^^       ^^^^^^^

prints

b is not equal to c

Doing something similar on C (the language) actually irritated the compiler due to the different types of the pointers compared.

I got:

warning: comparison of distinct pointer types lacks a cast [enabled by default]

Solution 5 - C++

In computing (or, rather, we should say in mathematics) there can be many notions of equality. Any relation that is symmetric, reflexive and transitive can be employed as equality.

In your program, you are examining two somewhat different notions of equality: bitwise implementation identity (two pointers being to exactly the same address) versus another kind of equality based on object identity, which allows two views on the same object, through references of different static type, to be properly regarded as referencing the same object.

These differently typed views use pointers which do not have the same address value, because they latch on to different parts of the object. The compiler knows this and so it generates the correct code for the equality comparison which takes into account this offset.

It is the structure of objects brought about by inheritance which makes it necessary to have these offsets. When there are multiple bases (thanks to multiple inheritance), only one of those bases can be at the low address of the object, so that the pointer to the base part is the same as the pointer to the derived object. The other base parts are elsewhere in the object.

So, naive, bitwise comparison of pointers would not yield the correct results according to the object-oriented view of the object.

Solution 6 - C++

Some good answers here, but there's a short version. "Two objects are the same" does not mean they have the same address. It means putting data into them and taking data out of them is equivalent.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionxmllmxView Question on Stackoverflow
Solution 1 - C++Mike SeymourView Answer on Stackoverflow
Solution 2 - C++Arne MertzView Answer on Stackoverflow
Solution 3 - C++luk32View Answer on Stackoverflow
Solution 4 - C++NobilisView Answer on Stackoverflow
Solution 5 - C++KazView Answer on Stackoverflow
Solution 6 - C++Isaac RabinovitchView Answer on Stackoverflow