“多态性”是C++最关键和核心的一个特性,“动态绑定技术”是C++编译器最重要的一个技术!欢迎C++高手进来指正我的粗浅理解

来源:互联网 发布:粒子群算法参数 编辑:程序博客网 时间:2024/05/01 18:10

我的一些粗浅理解,如下,理解错了,欢迎大家指正

程序,就是通过CPU指令,对内存中数据资源的操作,操作,也就是改变内存的二进制数,也是改变高低电平。

内存中,都是二进制数据,哪是指令,哪是数据,PC指令计数器所指向的内存单元,就是指令。

数据也是指令,指令也是数据,所以,程序只要管好PC寄存器就可以了

语言级上,程序,是由函数和数据组成,函数调用,实际上也是改变PC值,地址转移。

一个VC工程中,由许多H和CPP文件组成,

编译器,负责收集CPP文件(H文件被包含)中出现的所有标识或符号,负责形成逻辑、语法正确的代码

连接器,负责形成和调整函数的相对地址,并保证在CPP中,每次函数调用(也就是地址转移),都是有效的,每个对内存数据的访问的地址,是有效的。

最终翻译成汇编代码或机器代码。

为收集标识和符号,编译器维护一个符号映射表,有3栏(或3个字段):
1。类型栏(变量类型或函数类型,在编译时,由编译器负责填写)
2。名字栏(变量名或函数名,在编译时,由编译器负责填写)
3。地址栏(是相对地址,成员变量类型的这一栏就放偏移值,加载后,由操作系统重新调整,也就是地址重定位,在连接时,由连接器负责填写)

每声明定义一个变量或成员函数时,编译器就生成一个映射元素

如:

int a;

class A   

{

int m_a;

int m_b;

void fun1();

}

生成的两条映射表记录为:

生成的两条映射表记录为:

名字栏  |    类型栏    |  地址栏()

   a          |     int           |   指向a的声明定义处,相对地址,加载后,由操作系统重新调整

  A::m_a |  int             |  偏移地址0,运行时,实际地址:this + 0

  A::m_b |  int             |   偏移地址4,运行时,实际地址:this + 4

  A::fun1 | A::fun1()   |   指向A::fun1()定义处,相对地址,加栽后,由操作系统重新调整


有关编译器动态绑定技术,请看如下列:

class a
{

public:
  virtual fun1();

 virtual fun6();

  void fun2();

/*

出现virtual 关键字,编译器为该类创建虚表

  索引           |         函数指针

  0                |     指向 a::fun1()定义处

  1               |     指向 a::fun6()定义处

出现成员函数声明,编译器填写映射表

名字栏       |    类型栏    |  地址栏

a::fun2()    |   a::fun2      | (由连接器填写)指向a::fun2()定义处

*/
}

//b继承自a

class b :public a

{

public:
  virtual fun1();

  void fun3();

/*

虚函数fun1():

重新定义,创建虚表,并继承了父类的虚表项,

修改了虚表中第一项(“a::fun1()函数”)的指针,使指向自己定义b::fun1()函数

  索引           |         函数指针

  0                |     指向 b::fun1()定义处(重定义)

 1                |     指向 a::fun6()定义处(没有重定义)


出现成员函数fun3()声明,映射表中增加一栏:

名字栏       |    类型栏    |  地址栏

b::fun3()    |   b::fun3     | 指向b::fun3()定义处

从父类中继承的成员函数fun2()

映射表中增加一栏:记录从a继承的fun2()函数

名字栏       |    类型栏    |  地址栏

b::fun2()    |   a::fun2    | 指向父类a::fun2()定义处

*/
}

//c继承自a
class c :public a

{

public:
  virtual fun6();

 void fun4();

/*

虚函数fun1():

重新定义,创建虚表,并继承了父类的虚表项,

修改了虚表中第二项(“a::fun6()函数”)的指针,使指向自己定义c::fun6()函数

  索引           |         函数指针

  0                |     指向 a::fun1()定义处

  1                |     指向 c::fun6()定义处(改写)

出现 成员函数fun4(),映射表中增加一栏:

名字栏       |    类型栏    |  地址栏

c::fun4()    |   c::fun4     | 指向c::fun4()定义处

从父类中继承的成员函数fun2()

映射表中增加一栏:记录从a继承的fun2()函数

名字栏       |    类型栏    |  地址栏

c::fun2()    |   a::fun2    | 指向父类a::fun2()定义处

*/

}

调用
main()
{
  b     var1;
  c     var2;
  a*    p;
 
 p = &var1;
 

var1.fun2();

/*

var1.fun2()调用,静态绑定:

编译到此处时,编译器到映射表中找名字栏,找到b::fun2()名字(由于var1为b类型),其对应的类型栏为“函数类型,类型名为a::fun2(因为此函数由a类型定义)”,其地址栏的指针值为“指向a::fun2()定义处”,所以,此处函数调用,被编译器替换为“转向:地址栏的指针值”,实际上可理解为是修改指令记数器的值为“地址栏的指针值”

*/

*/

p->fun2();

/*

p->fun2(),静态绑定:这个容易理解,编译时,是找“a::fun2()”名字(因为p是a类型)

*/

p->fun2(),静态绑定:这个容易理解,编译时,是找“a::fun2()”名字(因为p是a类型)

*/

p->fun2(),静态绑定:这个容易理解,编译时,是找“a::fun2()”名字(因为p是a类型)

*/

p->fun3();

/*

 p->fun3(),这个调用,可能要发生编译错误,因为类型a没有声明和定义fun3()函数,找不到a::fun3()名字,只有b::fun3()名字

*/

p->fun1();

/*
p->fun1();fun1()是个虚函数,同理,编译到此处时,编译器是不是也到映射表中找“a::fun1()”名字呢?不是的。
因为,在映射表中,是找不到a::fun1()”这个名字的,因为,fun1()名字声明前有关键字"virtual",
在类声明和定义时,编译器,为每个出现virtual关键字的类,维护一个全局数据结构“虚表”

虚表:

1。索引;2。函数指针(这值在子类重写虚函数后,发生相应变化)


所以上面调用,在编译器发现调用的是“虚函数”时,
编译器做了如下处理:

将p->fun1()  替换为:p->vptr[offset],

vptr名字为虚表指针:
由编译器维护(上面提到),每个含虚函数的类,其生成对象,在内存中,首先的四个字节就是vptr,这个值是静态的,同一个类型的所有对象的vptr值相同,指向同一个虚表;

offset值随虚函数声明次序而定,如果第一个声明,索引则为0,出现在虚表的第一项,第二个声明则为1,以次。。。(此时offset 为 0 即是:p->vptr[0])

p为基类,可指向子类的任何对象

(附加:

1。类型转换实际是:内存切割,管辖内存从大变小,从大变小,现实世界中,是允许,在C++中,也是允许的;编译器不允许基类对象向子类对象的转化,因为从小变大,会导致内存访问越界。

  2。指针的类型,实际上决定了通过该指针能访问的内存范围

),

所指向对象的类型不同,vptr不同
 所以,p->vptr[0],函数调用地址值在编译时,是不可能确定,主要因为
p指针所指向对象不能确定(p是指向b,还是指向c?还是其他。。,但offset是可以确定的)
从而,vptr值不能确定,
直到程序运行时,p->vptr[0]调用的函数地址,视p所指向对象类型而定
如果p指向b子类,则p指向内存中的首4字节的vptr指向b的虚表,虚表第一项的指针指向自己函数定义的地址处
如果p指向c子类,则。。。。
这种直到运行时,才能确定函数调用地址的方式,即为:“动态绑定”

*/

p->fun6();

/*

同上,由于b类未改写需函数fun6(),所以该处调用,实际调用a::fun6(),编译器做如下处理:

p->fun6() 变为  p->vptr[1],由于,fun6为第二声明,此时,offset为1

*/

p = &var2;
 p->fun1();//动态绑定,同上, 调用a::fun1()
 p->fun2();//静态,同上

p->fun6();//调用c::fun6()
 p->fun4();
//编译出错,因为fun4()非虚函数,编译器在映射表中找不到a::fun4()名字,
//除非强行转换:((c*)p)->fun4(),

虚表:

1。索引;2。函数指针(这值在子类重写虚函数后,发生相应变化)


所以上面调用,在编译器发现调用的是“虚函数”时,
编译器做了如下处理:

将p->fun1()  替换为:p->vptr[offset],

vptr名字为虚表指针:
由编译器维护(上面提到),每个含虚函数的类,其生成对象,在内存中,首先的四个字节就是vptr,这个值是静态的,同一个类型的所有对象的vptr值相同,指向同一个虚表;

offset值随虚函数声明次序而定,如果第一个声明,索引则为0,出现在虚表的第一项,第二个声明则为1,以次。。。(此时offset 为 0 即是:p->vptr[0])

p为基类,可指向子类的任何对象

(附加:

1。类型转换实际是:内存切割,管辖内存从大变小,从大变小,现实世界中,是允许,在C++中,也是允许的;编译器不允许基类对象向子类对象的转化,因为从小变大,会导致内存访问越界。

  2。指针的类型,实际上决定了通过该指针能访问的内存范围

),

所指向对象的类型不同,vptr不同
 所以,p->vptr[0],函数调用地址值在编译时,是不可能确定,主要因为
p指针所指向对象不能确定(p是指向b,还是指向c?还是其他。。,但offset是可以确定的)
从而,vptr值不能确定,
直到程序运行时,p->vptr[0]调用的函数地址,视p所指向对象类型而定
如果p指向b子类,则p指向内存中的首4字节的vptr指向b的虚表,虚表第一项的指针指向自己函数定义的地址处
如果p指向c子类,则。。。。
这种直到运行时,才能确定函数调用地址的方式,即为:“动态绑定”

*/

p->fun6();

/*

同上,由于b类未改写需函数fun6(),所以该处调用,实际调用a::fun6(),编译器做如下处理:

p->fun6() 变为  p->vptr[1],由于,fun6为第二声明,此时,offset为1

*/

p = &var2;
 p->fun1();//动态绑定,同上, 调用a::fun1()
 p->fun2();//静态,同上

p->fun6();//调用c::fun6()
 p->fun4();
//编译出错,因为fun4()非虚函数,编译器在映射表中找不到a::fun4()名字,
//除非强行转换:((c*)p)->fun4(),

虚表:

1。索引;2。函数指针(这值在子类重写虚函数后,发生相应变化)


所以上面调用,在编译器发现调用的是“虚函数”时,
编译器做了如下处理:

将p->fun1()  替换为:p->vptr[offset],

vptr名字为虚表指针:
由编译器维护(上面提到),每个含虚函数的类,其生成对象,在内存中,首先的四个字节就是vptr,这个值是静态的,同一个类型的所有对象的vptr值相同,指向同一个虚表;

offset值随虚函数声明次序而定,如果第一个声明,索引则为0,出现在虚表的第一项,第二个声明则为1,以次。。。(此时offset 为 0 即是:p->vptr[0])

p为基类,可指向子类的任何对象

(附加:

1。类型转换实际是:内存切割,管辖内存从大变小,从大变小,现实世界中,是允许,在C++中,也是允许的;编译器不允许基类对象向子类对象的转化,因为从小变大,会导致内存访问越界。

  2。指针的类型,实际上决定了通过该指针能访问的内存范围

),

所指向对象的类型不同,vptr不同
 所以,p->vptr[0],函数调用地址值在编译时,是不可能确定,主要因为
p指针所指向对象不能确定(p是指向b,还是指向c?还是其他。。,但offset是可以确定的)
从而,vptr值不能确定,
直到程序运行时,p->vptr[0]调用的函数地址,视p所指向对象类型而定
如果p指向b子类,则p指向内存中的首4字节的vptr指向b的虚表,虚表第一项的指针指向自己函数定义的地址处
如果p指向c子类,则。。。。
这种直到运行时,才能确定函数调用地址的方式,即为:“动态绑定”

*/

p->fun6();

/*

同上,由于b类未改写需函数fun6(),所以该处调用,实际调用a::fun6(),编译器做如下处理:

p->fun6() 变为  p->vptr[1],由于,fun6为第二声明,此时,offset为1

*/

p = &var2;
 p->fun1();//动态绑定,同上, 调用a::fun1()
 p->fun2();//静态,同上

p->fun6();//调用c::fun6()
 p->fun4();
//编译出错,因为fun4()非虚函数,编译器在映射表中找不到a::fun4()名字,
//除非强行转换:((c*)p)->fun4(),

p->fun6();//调用c::fun6()
 p->fun4();
//编译出错,因为fun4()非虚函数,编译器在映射表中找不到a::fun4()名字,
//除非强行转换:((c*)p)->fun4()

}

}
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 京东买的货没收到怎么办 淘宝物流显示已揽件就是不动怎么办 淘宝查不到物流信息怎么办 快递物流信息更新错怎么办 淘宝上查不到物流怎么办 微信买的东西不给退怎么办 微信购物已收货怎么办 微信买东西不退怎么办 银行经营贷款资金回流怎么办 淘宝有运费险换货怎么办 淘宝有运费险的换货怎么办 淘宝换货一直不发货怎么办 淘宝申请换货卖家不发货怎么办 淘宝买家泄露卖家信息怎么办 高仿苹果没内存怎么办 高仿苹果7太卡怎么办 天猫客服处理不了怎么办 美团顾客电话打不通怎么办 美团众包顾客电话打不通怎么办 天猫退货商家拒绝退款怎么办 中关村买电脑被骗了怎么办 在闲鱼被买家骗了东西怎么办 在手机店买手机被骗怎么办 买手机分期付款被骗了怎么办 买电脑被骗了怎么办啊 小米卡puk锁了怎么办 红米3x电池松动怎么办 实体店买到苹果翻新机怎么办 小米手环2米粒掉怎么办 小米字体下架了怎么办 京东白条被锁定怎么办 苹果6splus很卡怎么办 苹果6s卡槽坏了怎么办 在京东买的电脑没发票怎么办 京东上买的空调没发票怎么办 分期手机被坑了怎么办 京东退货发票丢了怎么办 买东西的发票丢了怎么办 淘宝买东西发票丢了怎么办 网上买的手机没有发票怎么办 网上买的手机没发票怎么办