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.