12

I'm writing some code involving inheritance from a basic ref-counting pointer class; and some intricacies of C++ popped up. I've reduced it as follows:

Suppose I have:

class A{};
class B{};
class C: public A, public B {};

C c;
C* pc = &c;
B* pb = &c;
A* pa = &c;

// does pa point to a valid A object?
// does pb point to a valid B object?

// does pa == pb ?

Furthermore, does:

// pc == (C*) pa ?
// pc == (C*) pb ?

Thanks!

Trent
  • 13,249
  • 4
  • 39
  • 36
anon
  • 41,035
  • 53
  • 197
  • 293
  • 2
    what do you mean by ==? Obviously, the addresses are the same. – Dave Bacher Jan 28 '10 at 19:04
  • 9
    The addresses are not necesarrily the same. – John Dibling Jan 28 '10 at 19:09
  • 1
    In one implementation of multiple inheritance (virtual) which i have seen, some offset is added to base address of derived class's object to access the attributes and methods of B. However this is not visible to the user as compiler manipulates it internally. Hence in your program you may still see that the address of A, B and C are same. – mukeshkumar Jan 28 '10 at 19:10
  • @hype: The operative word here being "may." – John Dibling Jan 28 '10 at 19:25
  • @Dave Bacher This isn't true all the time. I personally had a segfault a while back while trying to dereference a pointer of one that pointed to an object, but the pointer had a different type than what the function expected. However, the object inherited from both types. It may work, but it isn't safe. – San Jacinto Jan 28 '10 at 19:43

5 Answers5

10
  • does pa point to a valid A object?
  • does pb point to a valid B object?

Yes, the C* gets converted so that pa and pb point to the correct addresses.

  • does pa == pb ?

No, usually not. There can't be an A object and a B object at the same address.

Furthermore, does

  • pc == (C*) pa ?
  • pc == (C*) pb ?

The cast converts the pointers back to the address of the C object, so both equalities are true.

Community
  • 1
  • 1
sth
  • 222,467
  • 53
  • 283
  • 367
  • 1
    `dynamic_cast` is inappropriate here and C-style is correct (albeit bad form). According to §5.4/7, C-style casting may invoke a `static_cast` where "— a pointer to an object of non-virtual base class type, an lvalue of non-virtual base class type, or a pointerto member of non-virtual base class type may be explicitly converted to a pointer, a reference, or a pointer to member of a derived class type, respectively." – Potatoswatter Jan 28 '10 at 19:25
  • Yeah, you are right, the dynamic cast would only be needed if the `pa` would be casted to a `B*`, for example. – sth Jan 28 '10 at 20:35
  • In that case, the `dynamic_cast` expression would just be a fancy way to say `NULL`. An A* can't be a B* so it would always fail. – Potatoswatter Jan 28 '10 at 20:39
  • @Potatoswatter: No, that would work, since `pa` really is a `C*`, and a `C*` can be converted to a `B*`. But to figure that out a `dynamic_cast` is needed. – sth Jan 28 '10 at 20:41
  • @sth: If `pa` is a `C*` but you know it points to an `A`, then you can and should use `static_cast`. `static_cast` and `dynamic_cast` solve different problems; `dynamic_cast` is irrelevant here and unusable unless he adds virtual destructors. – Potatoswatter Jan 28 '10 at 20:54
  • `does pa == pb` In this question the answer is yes. The standard does state that complete objects must have a unique address, pointers to base classes can have the same value. In fact, since pointers are just addresses, you can have many pointers of various types point at the same address. – Skizz Jan 28 '10 at 23:16
  • 2
    @Skizz: Assume by default that the base classes aren't empty. A newbie doesn't need to be misled by a technicality about his example. `pa == pb` is UNDEFINED, the answer isn't a definite yes anyway. – Potatoswatter Jan 29 '10 at 03:51
  • @Potatoswatter: regarding your '@sth: if pa is a C* but...' comment. I think you have it backward here. `pa` is of type `A*` but points to an object of dynamic type `C` which inherits from both `A` and `B`. Since going to `B*` involves first passing through `C*` it requires a dynamic cast to ensure that `pa` does not point to a straight `A` object. – Matthieu M. Jan 29 '10 at 07:46
  • @Matthieu: You're right, I got A and C mixed up in that comment. You can certainly cast between pointers to base types using RTTI. Doesn't relate to the OP though, and you need a virtual destructor. And, if `pa` is an `A*` but you know it points to a `C`, you can still get a `B*` with *two* `static_cast`s and no vtable. – Potatoswatter Jan 29 '10 at 10:30
  • @Potatoswatter: sure enough, I tend to favor `dynamic_cast` over `static_cast` for upcasting though, just to avoid a stupid mistake :x – Matthieu M. Jan 29 '10 at 15:29
  • @Potatoswatter in fact `pa == pb` is **ill-formed** as `A *` and `B *` do not have a *composite pointer type* as described by [expr/13] (N3936). It's only "undefined" in the sense that execution of anything resulting from an ill-formed program is completely undefined. – M.M Mar 21 '15 at 23:22
  • @MattMcNabb It's been a long time, but I think despite the code formatting, in the context of this discussion, it's supposed to be taken as an abstract equality, not compilable C++ code. In C++, `(void*)pa == (void*)pb` is undefined. – Potatoswatter Mar 22 '15 at 02:04
  • @Potatoswatter I think it'd be *unspecified* in that case. I guess other readers should bear in mind that cast to `(void *)` does actually make a difference, e.g. in the last part of this answer, `(C*)(void *)pb` may differ from `(C*)pb` . – M.M Mar 22 '15 at 02:14
  • @MattMcNabb Right, unspecified. I'm referring to the second part of this answer, "There can't be an A object and a B object at the same address." In fact there can, and it will usually be specified by the ABI. – Potatoswatter Mar 22 '15 at 03:32
5

Item 28 Meaning of Pointer Comparison in C++ Common Knowledge: Essential Intermediate Programming) explains the key of object pointer in C++:

In C++, an object can have multiple, valid addresses, and pointer comparison is not a question about addresses. It's a question about object identity.

Take a look at the code:

class A{};
class B{};
class C: public A, public B {};

C c;
C* pc = &c;
B* pb = &c;
A* pa = &c;

class C derives from both class A and class B, so class C is both class A and class B. the object C c has 3 valid addresses: address for class A, class B and class C. The implementation depends on compiler, so you can't assume the memory layout of class C, and it may like this:

 ----------  <- pc (0x7ffe7d10e1e0)
 |        |
 ----------  <- pa (0x7ffe7d10e1e4)
 | A data |
 ----------  <- pb (0x7ffe7d10e1e8)
 | B data |
 ----------
 | C data |
 ----------

In above case, although the address value of pc, pa and pb aren't same, they all refer to the same object (c), so the compiler must ensure that pc compares equal to both pa and pb, i.e., pc == pa and pc == pb. The compiler accomplishes this comparison by adjusting the value of one of the pointers being compared by the appropriate offset. E.g.,

pc == pa

is translated to:

pc ? ((uintptr_t)pc + 4 == (uintptr_t)pa) : (pa == 0)

Among other things, since A and B have no inheritance relationship, we can't compare pa and pb directly.

For your questions:

(1) does pa point to a valid A object?  
(2) does pb point to a valid B object?  
Yes, refer the above diagram. 

(3) pc == (C*) pa ?  
(4) pc == (C*) pb ?  
Yes, No need to add (C*).

(5) does pa == pb ?
No. We can't compare them.
Nan Xiao
  • 16,671
  • 18
  • 103
  • 164
2

C embeds an A and a B.

class C: public A, public B {};

is very similar to the C code

struct C {
    A self_a;
    B self_b;
};

and (B*) &c; is equivalent to static_cast< B* >( &c ) is similar to &c.self_b if you were using straight C.

In general, you can't rely on pointers to different types being interchangeable or comparable.

Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
1
pc == pa;
pc == pb;

Not defined, depends on class structure.

pc == (C*) pa;
pc == (C*) pb;

Thats ok.

pa == pb;

No.

Do they point to valid objects?

Yes
George
  • 3,765
  • 21
  • 25
0

What you get is something like this in memory

 ----------
 | A data |
 ----------
 | B data |
 ----------
 | C data |
 ----------

So if you want the entire C object you'll get a pointer to the beginning of the memory. If you want only the A "part", you get the same address since that's where the data members are located. If you want the B "part" you get the beginning + sizeof(A) + sizeof(whatever the compiler adds for vtable). Thus, in the example, pc != pb (could be pc != pa) but pa is never equal to pb.

Francis Boivin
  • 255
  • 1
  • 7
  • 1
    I beg to differ on the `pc != pb`. Typing the question's code into DevStudio 2005, I get all pointers with the same value. And that's probably what the C++ standard says should happen, in this case - no virtual methods and no data members. The OP has posted another question where the base classes do have members, so that has a different answer, which is this answer more or less. – Skizz Jan 28 '10 at 23:22
  • @Skizz: what you're seeing is called the Empty Base Class optimization http://www.google.com/search?q=empty+base+class+optimization it's implemented by all modern compilers but is far from being required. – Potatoswatter Jan 29 '10 at 03:57
  • 1
    class A{}; class B{}; class C: public A, public B{}; int main() { C c; C* pc(&c); A* pa(pc); B* pb(pc); printf("C=0x%08X\nB=0x%08X\nA=0x%08X\n", pc, pb, pa); } output from VS2008 Release build C=0x0018FEFF B=0x0018FF00 A=0x0018FEFF If you change the order in which C inherits from A and B, you'll get different values for A and B – Francis Boivin Jan 29 '10 at 11:49