1

I was given this code to test my cpp understanding, and I am quite confused:

#include "stdafx.h"
#include <iostream>
#include <cstddef>

using namespace std;

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 _tmain(int argc, _TCHAR* argv[])
{
    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;
}

The answer is 0204. I have understand the first 3cases, but not the last one. The difference between the last and first is a virtual desctructor. Is this related? If yes, how?

pidabrow
  • 966
  • 1
  • 21
  • 47
user1866880
  • 1,437
  • 6
  • 18
  • 26
  • [Inside the C++ Object Model](http://www.amazon.co.uk/dp/0201834545/) covers this question and a lot more. – DCoder Jan 19 '13 at 13:23

2 Answers2

3

The code example has an implementation defined behavior. The output for any of the cases cannot be guaranteed. It is not guaranteed that members of a class are always placed in contiguous memory locations.There can be padding bytes added in between them. And whether or not padding is added is left out as an implementation detail. Your suspicion of virtual playing a role just might be true[Note 1:]. But the important thing to note is even without the virtual the output is not guaranteed.

Reference:
C++11: 9.2 Class members [class.mem]

14) Nonstatic data members of a (non-union) class with the same access control (Clause 11) are allocated so that later members have higher addresses within a class object. The order of allocation of non-static data members with different access control is unspecified (11). Implementation alignment requirements might cause two adjacent members not to be allocated immediately after each other; so might requirements for space for managing virtual functions (10.3) and virtual base classes (10.1).


[Note 1]:
Dynamic dispatch itself is a implementation defined mechanism but most(read all known) implementations use the virtual table and pointer mechanism to implement it.
In case of a polymorphic class(which does not derive from any other class) usually the virtual pointer is stored as the first element of class. So it is reasonable to assume that this is what happens behind the scenes in the last case when you run the code sample on your environment.

Online sample:

#include<iostream>
using std::cout;
using std::endl;

class B;
typedef void (*HANDLE_DOSOMETHING)(B *const, int q);

class B
{
public:
  virtual void doSomething(int q)
  {
      std::cout<<"B::doSomething()"<<q<<endl;
  }
  void dummy()
  {
      HANDLE_DOSOMETHING *f1ptr = NULL;
      int                *vtbl  = NULL;
      int                *vptr  = (int *)this; // address of the object

      vtbl = (int *)*vptr; //address of the VTABLE

      f1ptr = (HANDLE_DOSOMETHING *)&(vtbl[0]); //address of the 1st virtual function
      (*f1ptr)(this, 55);
   }
};
int main()
{
    B objb;
    objb.dummy();
    return 0;  
}

Output:

B::doSomething()55
Community
  • 1
  • 1
Alok Save
  • 202,538
  • 53
  • 430
  • 533
1

Yes, the virtual member function(s) (in this case a destructor) are held in a "vtable", which is often stored as the first element(s) of the class data structure.

However, be aware that there is no strict rules in the C++ standard that says that this is the case.

Mats Petersson
  • 126,704
  • 14
  • 140
  • 227