bash$cat class.cpp #include<iostream> class Base { int i; float f; }; bash$g++ -c class.cpp
我跑了
bash$objdump -d class.o bash$readelf -a class.o
但我得到的是很难让我明白.
有人可以解释一下,或者提出一些很好的起点.
谢谢,
Jagrati
解决方法
就继承而言,请引用C FAQ LITE,这是镜像在这里的http://www.parashift.com/c++-faq-lite/virtual-functions.html#faq-20.4.本章将介绍如何在实际硬件中调用虚拟函数(编译在机器代码中做什么).
让我们举个例子.假设类Base有5个虚函数:virt0()到virt4().
// Your original C++ source code class Base { public: virtual arbitrary_return_type virt0(...arbitrary params...); virtual arbitrary_return_type virt1(...arbitrary params...); virtual arbitrary_return_type virt2(...arbitrary params...); virtual arbitrary_return_type virt3(...arbitrary params...); virtual arbitrary_return_type virt4(...arbitrary params...); ... };
步骤1:编译器构建一个包含5个函数指针的静态表,将该表埋入某个静态内存中.许多(并非所有)编译器在编译定义Base的第一个非内联虚拟函数的.cpp时定义此表.我们称该表为v表;我们假装它的技术名称是Base :: __ vtable.如果函数指针适合目标硬件平台上的一个机器字,则Base :: __ vtable将最终消耗5个隐藏的内存字.每个不是5个,而不是每个功能的5个;只是5.它可能看起来像下面的伪代码:
// Pseudo-code (not C++,not C) for a static table defined within file Base.cpp // Pretend FunctionPtr is a generic pointer to a generic member function // (Remember: this is pseudo-code,not C++ code) FunctionPtr Base::__vtable[5] = { &Base::virt0,&Base::virt1,&Base::virt2,&Base::virt3,&Base::virt4 };
步骤#2:编译器为Base类的每个对象添加一个隐藏指针(通常也是一个机器字).这被称为v指针.将这个隐藏的指针看作是一个隐藏的数据成员,好像编译器将你的类重写为如下所示:
// Your original C++ source code class Base { public: ... FunctionPtr* __vptr; ← supplied by the compiler,hidden from the programmer ... };
步骤3:编译器在每个构造函数内初始化这个> __ vptr.这个想法是使每个对象的v指针指向其类的v表,就像在每个构造函数的init-list中添加以下指令一样:
Base::Base(...arbitrary params...) : __vptr(&Base::__vtable[0]) ← supplied by the compiler,hidden from the programmer ... { ... }
现在我们来解决一个派生类.假设你的C代码定义了从类Base继承的类Der.编译器重复步骤#1和#3(但不是#2).在步骤#1中,编译器创建一个隐藏的v表,保持与Base :: __ vtable中相同的功能指针,但替换与覆盖对应的那些插槽.例如,如果Der通过virt2()覆盖了virt0(),并以其他方式继承,那么Der的v表可能看起来像这样(假装Der不添加任何新的虚拟):
// Pseudo-code (not C++,not C) for a static table defined within file Der.cpp // Pretend FunctionPtr is a generic pointer to a generic member function // (Remember: this is pseudo-code,not C++ code) FunctionPtr Der::__vtable[5] = { &Der::virt0,&Der::virt1,&Der::virt2,&Base::virt4 }; ^^^^----------^^^^---inherited as-is
在第3步中,编译器在Der的构造函数的开头添加类似的指针赋值.这个想法是改变每个Der对象的v指针,使其指向其类的v表. (这不是第二个v指针;它是在基类Base中定义的相同的v指针;请记住,编译器不会在类Der中重复第2步.)
最后,我们来看看编译器如何实现对虚函数的调用.您的代码可能如下所示:
// Your original C++ code void mycode(Base* p) { p->virt3(); }
编译器不知道这是否会调用Base :: virt3()或Der :: virt3(),或者还有另外一个派生类的virt3()方法,甚至还不存在.它只知道你正在调用的是virt3(),它恰好是v表中插槽#3的功能.它把这个电话改写成这样的东西:
// Pseudo-code that the compiler generates from your C++ void mycode(Base* p) { p->__vptr[3](p); }
我强烈建议每个C开发人员阅读FAQ.这可能需要几个星期(因为很难阅读和漫长),但它会教你很多关于C和可以做什么.