How does this program work?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#include <cstddef>
#include <iostream>

class A
{
public:
    A() : m_x(0) { }

public:
    static ptrdiff_t member_offset(const A &a)
    {
        const char *p = reinterpret_cast<const char*>(&a);
        const char *q = reinterpret_cast<const char*>(&a.m_x);

        return q - p;
    }

private:
    int m_x;
};

class B
    : public A
{
public:
    B() : m_x('a') { }

public:
    static int m_n;

public:
    static ptrdiff_t member_offset(const B &b)
    {
        const char *p = reinterpret_cast<const char*>(&b);
        const char *q = reinterpret_cast<const char*>(&b.m_x);

        return q - p;
    }

private:
    char m_x;
};

int B::m_n = 1;

class C
{
public:
    C() : m_x(0) { }
    virtual ~C() { }

public:
    static ptrdiff_t member_offset(const C &c)
    {
        const char *p = reinterpret_cast<const char*>(&c);
        const char *q = reinterpret_cast<const char*>(&c.m_x);

        return q - p;
    }

private:
    int m_x;
};

int main()
{
    A a;
    B b;
    C c;
    std::cout << ((A::member_offset(a) == 0) ? 0 : 1);
    std::cout << ((B::member_offset(b) == 0) ? 0 : 2);
    std::cout << ((A::member_offset(b) == 0) ? 0 : 3);
    std::cout << ((C::member_offset(c) == 0) ? 0 : 4);
    std::cout << std::endl;

    return 0;
}


Can someone please explain how the above program output this => 0204?
Many thanks.
std::cout << ((A::member_offset(a) == 0) ? 0 : 1);
is the same as
1
2
3
4
if ( A::member_offset(a) == 0 );
    std::cout << '0';
else
    std::cout << '1';
Before I start:

You are relying on undefined behavior. The program might output 0204 or it might output something entirely different.

You really should never be doing whatever it is you're trying to learn from this example.




But... in the interest of education... here is what's happening. Please note that I'm saying "might" and "usually" a lot here.. because none of this is guaranteed. It just happens to be working out this way for you. Again... never do this in a real program:

You have 3 classes. Each of these classes are arranged in memory somehow. What this program is doing is looking at the address of a specific member and comparing it to the address of the object as a whole. This will only be true when the very first thing stored in the class is the member in question.

To illustrate this... here is how the 'A' class might look in memory: (again note I'm saying "might" because it's not guaranteed)


A has one member:  m_x


Address of A object
|
v
+---------+
| A::m_x  |
+---------+
^
|
Address of A::m_x


In this case, the A object only has 1 member, m_x... and has no other information. Therefore the address of the A object is the same as the address of the A::m_x member. As a result, this line:

std::cout << ((A::member_offset(a) == 0) ? 0 : 1);

...might print 0


The B class is a little trickier, since it not only has its own m_x member, but it also derives from A. Therefore it also contains everything that A contains. Here is how the 'B' class might look in memory:


A has one member:  m_x   (A::m_x)
B has one member:  m_x   (B::m_x)
B also derives from A, so it also contains A::m_x

note we ignore B::m_n here because it is static and therefore is not stored globally
and not part of each individual object.


Address of A object (and B object)
|
v
+---------+----------+
| A::m_x  | B::m_x   |
+---------+----------+
^         ^
|         |
|         Address of B::m_x
|
Address of A::m_x


In this case, the B object's B::m_x member is not placed at the start of the object because A::m_x is at the start of the object. Therefore this line:

std::cout << ((B::member_offset(b) == 0) ? 0 : 2);

...might print 2



On the other hand... the A::m_x member in this example is placed at the start of the object. Therefore this line:

std::cout << ((A::member_offset(b) == 0) ? 0 : 3);

...might print 0



Lastly, the C class has 1 member: m_x. However, it also contains at least 1 virtual function (a virtual destructor). Therefore the class is polymorphic and contains a vtable. Usually, the vtable is placed at the start of the object. So here is how C might look in memory:


C has one member:  m_x
C is also polymorphic, so it has a vtable

Address of C object
|
v
+---------+----------+
| vtable  | C::m_x   |
+---------+----------+
          ^
          |
          Address of C::m_x


Since m_x is not stored at the start of the C object (the vtable is), this line:

std::cout << ((C::member_offset(c) == 0) ? 0 : 4);

...might print 4.
Last edited on
@Disch

Thank you very much for your detailed explanation.
It was very clear.

I thought I knew C++.

Many thanks.
Topic archived. No new replies allowed.