加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

一个编译的C类怎么样?

发布时间:2020-12-16 05:43:39 所属栏目:百科 来源:网络整理
导读:在汇编指令和C程序中有一些背景,我可以看出编译函数的外观是什么样的,但很有趣的是,我从来没有仔细想过如何编译C类. bash$cat class.cpp#includeiostreamclass Base{int i;float f;};bash$g++ -c class.cpp 我跑了 bash$objdump -d class.obash$readelf -a c
在汇编指令和C程序中有一些背景,我可以看出编译函数的外观是什么样的,但很有趣的是,我从来没有仔细想过如何编译C类.
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

解决方法

这些类(或多或少)被构造为常规结构体.方法(或多或少…)转换为第一个参数为“this”的函数.对类变量的引用完成为“this”的偏移量.

就继承而言,请引用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和可以做什么.

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读