72

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"

xmllmx
  • 39,765
  • 26
  • 162
  • 323
  • 13
    I don't see what this question has to do with C (the language). The title and tags seem inconsistent with the body of the question. – CB Bailey Aug 16 '13 at 12:05
  • And what exactly is the weird result you're gettting? – molbdnilo Aug 16 '13 at 12:08
  • In C, two pointers are compared only by its addresses; while C++ does not always so. – xmllmx Aug 16 '13 at 12:08
  • @molbdnilo, 0x003E9A9C is not equal to 0x003E9A98, but the output is "b is equal to c". – xmllmx Aug 16 '13 at 12:09
  • 6
    I fixed the title, please review and rollback if you disagree. – jrok Aug 16 '13 at 12:10
  • 1
    @xmllmx you should use `cout << "The address of c is 0x" << hex << static_cast(c) << endl;` because that is what `b==c` will do with the right side. – PeterT Aug 16 '13 at 12:16
  • 2
    @PeterT that is what the whole question is about and what obviously suprised the OP: why the raw addresses are different, while direct comparison says otherwise. the cast would only hide the surprising difference, not explain it ;-) – Arne Mertz Aug 16 '13 at 12:24
  • 2
    I'd like to spank the four twits who close voted this. The notion of equality (what it means and how many kinds can exist) is central to mathematics and computing. – Kaz Aug 16 '13 at 18:54
  • @Kaz: Initially, the title was gibberish and the question lacked a question. It's a shame we can't remove the close votes now it's been fixed. – Mike Seymour Aug 16 '13 at 21:16
  • @MikeSeymour I see; they are all "unclear what you're asking" votes. The current workflow is that the closing must first complete, and then the question can be reopened if edited. – Kaz Aug 16 '13 at 21:25
  • @ArneMertz Late to the party. Still: PeterT's cast would not hide anything but make the inner workings of the language explicit. The equality operator works as expected and indeed returns true only for identical addresses: "Two pointers of the same type compare equal if and only if they [...] both represent the **same address.**" The language is terse about the type restrictions: "Pointers of the **same type** (after pointer conversions) can be compared for equality." The only (implicit) conversion sequence with that result is the static_cast(c). – Peter - Reinstate Monica Sep 27 '21 at 09:21

6 Answers6

87

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.

Mike Seymour
  • 249,747
  • 28
  • 448
  • 644
  • Would casting to a `ptrdiff_t` before the comparison avoid the conversion that is causing the equality? Or maybe more correctly, is it a legal application of a `ptrdiff_t` cast? Or should it be a `void*`? – jww Feb 22 '18 at 06:21
  • Nitpick: Not "at most", but *exactly* one of the two sub-objects (namely the first one) must have the same address as the top-level object. That is a language requirement. – Peter - Reinstate Monica Sep 27 '21 at 09:24
77

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.

DisplayName
  • 3,093
  • 5
  • 35
  • 42
Arne Mertz
  • 24,171
  • 3
  • 51
  • 90
  • Worth noting that, as you describe well, the class layout must be known for the necessary conversion. Even though we only deal with pointers the comparison will not compile with incomplete (e.g., forward declared) classes. – Peter - Reinstate Monica Sep 27 '21 at 09:30
8

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.

luk32
  • 15,812
  • 38
  • 62
7

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]
Nobilis
  • 7,310
  • 1
  • 33
  • 67
2

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.

Kaz
  • 55,781
  • 9
  • 100
  • 149
0

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.

  • 1
    A story I want to share that doesn't belong in the answer. Once being interviewed for a tech writing job, I was asked to explain what a pointer is in C. My answer involved type-checking, dereferencing and so on. This was considered the too nitpicky; the answer they were looking for was "a pointer is a memory address". I think this example shows that that answer is too simplistic. – Isaac Rabinovitch Aug 22 '13 at 20:36
  • Yes and no. As the standard notes, "two pointers of the same type compare equal if and only if they represent the same address". And equality comparison requires the same type. The necessary, implicit conversion *changes* the memory location pointed to before operator==() sees it; it is then, indeed, equal to the other. – Peter - Reinstate Monica Sep 27 '21 at 09:33