×

Loading...
Ad by
  • 推荐 OXIO 加拿大高速网络,最低月费仅$40. 使用推荐码 RCR37MB 可获得一个月的免费服务
Ad by
  • 推荐 OXIO 加拿大高速网络,最低月费仅$40. 使用推荐码 RCR37MB 可获得一个月的免费服务

you two do not understand vtable. An object holds hidden vtable pointer, not vtable.

struct classA
{
int a;
};

printf("%d", sizeof(classA)); ===> 4 sizeof(int)

struct classB
{
int a;
virtual aMethod() { }
virtual bMethod() { }
};

printf("%d", sizeof(classB)); ===> 8 sizeof(int) + sizeof(void*)

the extra 4 bytes (or 8 bytes at 64-bit os) stores the pointer to the vtable. this pointer is setup before constructor is called.

classB obj;

System first allocate 8 bytes, then set its vtable pointer, Then constructor is called.
Report

Replies, comments and Discussions:

  • 工作学习 / 学科技术讨论 / 问两个C++问题,都是面试时遇到的。
    1. How to ensure the memory allocated is free? (In Java, there is garbage collector, but in C++, how to ensure memory are free?)

    2. In a class constructor, other virtual member functions can be called? (e.g, class A has a virtual function called init, in A's constructor, init can be called? why?)
    • What type of job are you looking for? Don't tell us it's heavily C++ related.
      • 这还不是真正的面试,只是个电话screen,其他问题我都答的不错,就这两个问题,我一时有些蒙。之前参加了这个公司的一个变态C++考试,挺难但我也考过了。结果这两个问题答的不好,人家觉得我不够strong.
      • 第一个问题我说的挺多,但对方不满意,他提到了一个resource什么的,我没听清,但应该是我不知道的。第二个问题我说不要在constructor, destructor中调用virtual function, 但不知道怎样解释好。
        这个工作用C++和数据库,数据库开发是我目前在干的,回答的还可以,但这两个问题答得不好,失掉了机会。你如果知道答案就告诉我,我也正在学习。
        • 1) Maybe he want's to know what do you know about the smart pointer.
          2) When you call virtual function in Contructor/Destructor, the virutal function defined in this class will be called not the one of the derived class, since the vtable is not fully set up.
        • Resource Acquisition Is Initialization?
      • First question, I guess they may expect smart pointer. The second question, I think your answer is correct, the reason is the virtual table may not be ready and the subclass constructor has not been called.
    • 1.Resource Pool. 2.你回答得不错。 P.S有人喜欢Boost/STL 。。。,有些人不喜欢。
      • NIHAO
    • For question 2 please correct me if I am wrong.
      Calling vitrual function depends on vtable at runtime. But vtable will NOT be constructed untill constructor returns. That's bad calling virtual function inside constructor means calling it (them) without vtable.
      • The vtable should has been created, but the the pointer to that table wont been created until the ctor done.
        • Both are wrong. The vtable pointer is always created before constructor, and vtable is fixed, filled by the compiler. The real problem is due to the fact that base class constructor is called before it entering its derived class constructor.
          In base class' constructor, if a virtual method is called, and passed to the derived, however, the derived class did not initialized yet (members are garbage). To avoid this behavior C++ standard requires that the virtual function is not passed down, which at the first glance does not make sense.
          • Thanks for the illustration, but I did not see any difference from what yout said. the derived class is not fully initialized yet, indicates not just the members are garbage, but also...
          • and actually, regarding to most compilers, "The vtable pointer is always created before constructor", this is wrong.
            • Cerboros Reborn is right.
              本文发表在 rolia.net 枫下论坛Both the vtable and vptr are created in compile time and already within the binary image file. Constructor does NOTHING with filling up vtable slots and asigning vptr.

              Now I am curious that it will be NOT a big issue for compiler to resolve this problem at compile time. In general, is it make sence that calling virtual member function within the member function can be resolved at compile time? For example,

              class Base
              {
              public:
              Base();
              virtual ~Base();

              virtual void foo(){printf("Base::foo()");};
              virtual void bar(){printf("Base::bar()"); foo();};
              };

              class Derived : public Base
              {
              public:
              Derived();
              virtual ~Derived();

              virtual void foo() {printf("Derived::foo()");};

              virtual void bar() {printf("Derived::bar()"); foo();}
              };

              For outside Derived class,

              void test(Base* pB)
              {
              pB->bar(); // calling Base::bar() or Derived::bar() has to be resolved at runtime
              }

              However inside Base::bar() member function's body, calling foo() can be resolved at compile time as *this->vptr[2](this); (assume vptr[0] for RTTI and vptr[1] for virtual destructor). Same apply to Derived::bar() member function body for calling foo().

              For the same reason, as apply to special case, calling virtual function inside constructor also can be resolved at compile time. Thus what's the issue is?

              Just for testing, I play with MSVC and there is no problem.

              Do you guys have any specific scnario for calling virtual function inside constructor fails?更多精彩文章及讨论,请光临枫下论坛 rolia.net
              • If "Both the vtable and vptr are created in compile time and already within the binary image file. " holds water, then there is no problem calling any virtual function from the ctor. but again, is that real true? I do not think so.
              • Because vtable is not the problem here.
                class Derived : public Base
                {
                public:
                Derived() { a=0, foo(); }
                virtual ~Derived();

                virtual void foo() {printf("Derived::foo()");};

                private:
                int a;
                };

                The vtable is not an issue here. Before constructor is called, the vtable is already there. Now the problem is this:


                Derived::Derived() will call Base::Base() first before entering the constructor body.
                If Base::Base() calls a virtual function and the virtual function goes down to Derived.

                But Derived's data is not intialized - the constructor is not finished. so a will be filled the garbagge. It will cause hidden problems.

                So C++ standard says that if a class's constructor calls a virtual function, this function will not go down to derived. (i.e. vtable is not used).

                It is a choice chosen by C++ standard. C++ compile will issue a warning generaly if a virtual method is called within constructor, but it is not prohibited.
                • To illustrated the issue, considering this code:
                  class Base
                  {
                  public:
                  Base() : { init(); }
                  virtual void init() { printf("base::init."); }
                  };

                  class Derived
                  {
                  public:
                  Derived() : a(0) { }

                  virtual void init() { printf("derived::init"); }

                  int a;
                  };

                  Derived obj;

                  it should print:

                  base::init.
                  • Theoretically the output should be "Base::init", but it is NOT necessary ture in practice...
                    I've tested in VC++, the output is "derived::init" (you can veriry it yourself later).
                    The hidden issue here, as I stated in my previous reply, is relative to the "this" pointer?

                    Can you please give me some hint on this?
                    Thanks in advance!
                    • The theory is correct, and it is also true in practice. Otherwise I will go mad...
              • He is not right, that is what I mean, coz in thie regard, no C++ standard implementation exists.
              • but you are definitly not right on vtable.:)
            • "vtable pointer is always created before construcotr" is misleading. It looks like (not actually as)compare apple with orange :).
              • you two do not understand vtable. An object holds hidden vtable pointer, not vtable.
                struct classA
                {
                int a;
                };

                printf("%d", sizeof(classA)); ===> 4 sizeof(int)

                struct classB
                {
                int a;
                virtual aMethod() { }
                virtual bMethod() { }
                };

                printf("%d", sizeof(classB)); ===> 8 sizeof(int) + sizeof(void*)

                the extra 4 bytes (or 8 bytes at 64-bit os) stores the pointer to the vtable. this pointer is setup before constructor is called.

                classB obj;

                System first allocate 8 bytes, then set its vtable pointer, Then constructor is called.
                • your example has nothing to do with the sequence inside the ctor, since sizeof should be calculated and replace during compilation stage, but it does not show the calling sequence inside ctor.
                  • Did some search. Before Derived enters constructor, the vtable pointer is initialized to point to Base's vtable. After Base's constructor is finished, it is set back to its own vtable.
                    Not sure about this is required by the standard, but this implement makes sure that the derived's virtual function is not called inside base's constructor.

                    Again, it is valid to call virtual function from constructor, just the call is treated as non-virtual.
                • by the way, the following link illustrates what I mentioned "the vptr has not beedn created when calling the base ctor", and I believe most compiler, ie. g++ follows .....
    • 1)在编译构造函数时,编译器会自动加入指令将vptr指向当前类的vtable,并且这发生在由构造函数产生的任何指令之前; 2)如果在构造函数里调用虚函数,编译器会按照调用普通函数来处理,在这里并没有使用vptr;
      很简单的测试代码:

      class A
      {
      public:
      	A() : m_a(111) 
      	{ 
      		bar();
      		this->bar();
      		foo();
      	}
      	virtual void bar() { std::cout << "A::bar" << std::endl; }
      	void foo()
      	{
      		bar();
      	}
      
      	int m_a;
      };
      
      class B : public A
      {
      public:
      	B() : m_b(111)
      	{ 
      		bar();
      		this->bar();
      		foo();
      	}
      	virtual void bar() { std::cout << "B::bar" << std::endl; }
      
      	int m_b;
      };
      
      int _tmain(int argc, _TCHAR* argv[])
      {
      	B b;
      	return 0;
      }
      

      输出跟预想的一样:

      A::bar
      A::bar
      A::bar
      B::bar
      B::bar
      B::bar
      

      看一下在调用B::B()时编译器产生的代码在干啥:

      	B() : m_b(111)
      	{ 
      01251590  push        ebp  
      01251591  mov         ebp,esp 
      01251593  sub         esp,0CCh 
      01251599  push        ebx  
      0125159A  push        esi  
      0125159B  push        edi  
      0125159C  push        ecx  
      0125159D  lea         edi,[ebp-0CCh] 
      012515A3  mov         ecx,33h 
      012515A8  mov         eax,0CCCCCCCCh 
      012515AD  rep stos    dword ptr es:[edi] 
      012515AF  pop         ecx  
      012515B0  mov         dword ptr [ebp-8],ecx 
      012515B3  mov         ecx,dword ptr [this] 
      012515B6  call        A::A (1251140h) 
      012515BB  mov         eax,dword ptr [this] 
      012515BE  mov         dword ptr [eax],offset B::`vftable' (1257804h)  /// 在调用基类构造函数A::A()之后,这条指令将vptr指向B的vtable
      012515C4  mov         eax,dword ptr [this] 
      012515C7  mov         dword ptr [eax+8],6Fh /// <- 这里将m_b初始化为111
      		bar();
      012515CE  mov         ecx,dword ptr [this] 
      012515D1  call        B::bar (12511E5h) 
      		this->bar();
      012515D6  mov         ecx,dword ptr [this] 
      012515D9  call        B::bar (12511E5h) 
      		foo();
      012515DE  mov         ecx,dword ptr [this] 
      012515E1  call        A::foo (125128Fh) 
      	}
      

      注意上面红色的部分,它们告诉我们:

      1)在编译构造函数时,编译器会自动加入指令将vptr指向当前类的vtable,并且这发生在由构造函数产生的任何指令之前;
      2)如果在构造函数里调用虚函数,编译器会按照调用普通函数来处理,在这里并没有使用vptr;
      

      那么再看一下A::A()在干啥:

      	A() : m_a(111) 
      	{ 
      003C1BC0  push        ebp  
      003C1BC1  mov         ebp,esp 
      003C1BC3  sub         esp,0CCh 
      003C1BC9  push        ebx  
      003C1BCA  push        esi  
      003C1BCB  push        edi  
      003C1BCC  push        ecx  
      003C1BCD  lea         edi,[ebp-0CCh] 
      003C1BD3  mov         ecx,33h 
      003C1BD8  mov         eax,0CCCCCCCCh 
      003C1BDD  rep stos    dword ptr es:[edi] 
      003C1BDF  pop         ecx  
      003C1BE0  mov         dword ptr [ebp-8],ecx 
      003C1BE3  mov         eax,dword ptr [this] 
      003C1BE6  mov         dword ptr [eax],offset A::`vftable' (3C7810h)  
      003C1BEC  mov         eax,dword ptr [this] 
      003C1BEF  mov         dword ptr [eax+4],6Fh /// <- 这里将m_a初始化为111
      		bar();
      003C1BF6  mov         ecx,dword ptr [this] 
      003C1BF9  call        A::bar (3C11EAh)  
      		this->bar();
      003C1BFE  mov         ecx,dword ptr [this] 
      003C1C01  call        A::bar (3C11EAh)  
      		foo();
      003C1C06  mov         ecx,dword ptr [this] 
      003C1C09  call        A::foo (3C128Fh) 
      	}
      

      红色部分再次证实了编译器会在构造函数最前面插入指令将vptr指向当前类的vtable。有没有注意我们还在A的构造函数里调用了一个非虚函数foo(),这个foo()里调用了虚函数bar()?那么究竟哪个bar()会被调用呢?

      	void foo()
      	{
      003C16E0  push        ebp  
      003C16E1  mov         ebp,esp 
      003C16E3  sub         esp,0CCh 
      003C16E9  push        ebx  
      003C16EA  push        esi  
      003C16EB  push        edi  
      003C16EC  push        ecx  
      003C16ED  lea         edi,[ebp-0CCh] 
      003C16F3  mov         ecx,33h 
      003C16F8  mov         eax,0CCCCCCCCh 
      003C16FD  rep stos    dword ptr es:[edi] 
      003C16FF  pop         ecx  
      003C1700  mov         dword ptr [ebp-8],ecx 
      		bar();
      003C1703  mov         eax,dword ptr [this] 
      003C1706  mov         edx,dword ptr [eax] 
      003C1708  mov         esi,esp 
      003C170A  mov         ecx,dword ptr [this] 
      003C170D  mov         eax,dword ptr [edx] 
      003C170F  call        eax  
      003C1711  cmp         esi,esp 
      003C1713  call        @ILT+455(__RTC_CheckEsp) (3C11CCh) 
      	}
      

      显然这里用到了vptr,也就是说会调用vptr当前指向的vtable中的函数。既然foo()是在A的构造函数里调用的,这个时候vptr指向A::vtable,所以A::bar()会被调用。

      以上代码在VS2008里测试。

      • 谢谢!证实了我的猜测。构造函数里调用虚函数是用“this”而不是用“vptr”。可否推广到任何成员函数里调用虚函数都用“this”而不是用“vptr” ?
        • Your guess is wrong. vptr has been populated before ctors, and vptr is used in all functions except ctors and dtors.
    • C+++++++++++++++++++++啊 我就是去学JAVA 也不去学C佳佳,这个程序语言设计的太难了
      • Think Java is easy? Try dig into countless web frameworks, database wrappers and enterprise servers. At least C++ doesn't have so much frameworks and you can fly freely (and/or uncontrolled)...