以文本方式查看主题 - 计算机科学论坛 (http://bbs.xml.org.cn/index.asp) -- 『 C/C++编程思想 』 (http://bbs.xml.org.cn/list.asp?boardid=61) ---- 深入剖析:C++“多态性”在编译器中的实现(ZT) (http://bbs.xml.org.cn/dispbbs.asp?boardid=61&rootid=&id=25471) |
-- 作者:enorm -- 发布时间:12/15/2005 9:36:00 AM -- 深入剖析:C++“多态性”在编译器中的实现(ZT) 理论 如下内容,属个人的理解.与大家共享.错误之处,请指正 程序,就是通过CPU指令(CPU指令就是CPU能识别的二进制流,CPU通过解释指令,能发出各种电流脉冲,以达到控制其他电子电路的状态),对内存中数据资源的操作,也就是改变内存的二进制数,也是改变高低电平。 内存中,都是二进制数据,哪是指令,哪是数据? PC指令计数器所指向的内存单元,就是指令。 PC指向哪,哪就是指令,所以数据也是指令,指令也是数据,程序只要管好PC寄存器就可以了. 语言级上,程序,是由函数和数据组成,函数调用,实际上也是改变PC值,地址转移。 数据又分为两种: 一种就是在编译时,就分配地址空间.如全局数据 一种是在运行时,靠编译器所维护的栈顶指针,相对栈顶指针的偏移量,来分配地址空间.如局部数据. (另一种就是在堆中动态分配的) ------------------------------------------------------------------------------------------------------------------------------------- 一个VC工程中,由许多H和CPP文件组成, 编译器,负责收集CPP文件(H文件被包含在CPP内)中出现的所有标识或符号,并负责形成逻辑、语法正确的二进制代码(obj文件) 连接器,负责确定、调整函数的相对地址,并保证在CPP中,每次函数调用(也就是地址转移),都是有效的,每个对内存数据的访问的地址,是有效的。 最终由连接器形成翻译成汇编代码或机器代码。(DLL和EXE可执行的文件) 为收集标识和符号,编译器维护一个符号映射表,有3栏(或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 fun6(); void fun2(); /* 出现virtual 关键字,编译器为该类创建虚表 索引 | 函数指针 0 | 指向 a::fun1()定义处 1 | 指向 a::fun6()定义处 出现成员函数声明,编译器填写映射表 名字栏 | 类型栏 | 地址栏 a::fun2() | a::fun2 | (由连接器填写)指向a::fun2()定义处 */ //b继承自a class b :public a { public: void fun3(); /* 虚函数fun1(): 重新定义,创建虚表,并继承了父类的虚表项, 修改了虚表中第一项(“a::fun1()函数”)的指针,使指向自己定义b::fun1()函数 索引 | 函数指针 0 | 指向 b::fun1()定义处(重定义) 1 | 指向 a::fun6()定义处(没有重定义) 名字栏 | 类型栏 | 地址栏 b::fun3() | b::fun3 | 指向b::fun3()定义处 从父类中继承的成员函数fun2() 映射表中增加一栏:记录从a继承的fun2()函数 名字栏 | 类型栏 | 地址栏 b::fun2() | a::fun2 | 指向父类a::fun2()定义处 */ //c继承自a { public: 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()定义处 */ } 调用 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->fun2(),静态绑定:这个容易理解,编译时,是找“a::fun2()”名字(因为p是a类型) */ p->fun3(); /* p->fun3(),这个调用,可能要发生编译错误,因为类型a没有声明和定义fun3()函数,找不到a::fun3()名字,只有b::fun3()名字 */ p->fun1(); /* 虚表: 1。索引;2。函数指针(这值在子类重写虚函数后,发生相应变化) 将p->fun1() 替换为:p->vptr[offset], vptr名字为虚表指针: offset值随虚函数声明次序而定,如果第一个声明,索引则为0,出现在虚表的第一项,第二个声明则为1,以次。。。(此时offset 为 0 即是:p->vptr[0]) p为基类,可指向子类的任何对象 (附加: 1。类型转换实际是:内存切割,管辖内存从大变小,从大变小,现实世界中,是允许,在C++中,也是允许的;编译器不允许基类对象向子类对象的转化,因为从小变大,会导致内存访问越界。 2。指针的类型,实际上决定了通过该指针能访问的内存范围 ), 所指向对象的类型不同,vptr不同 */ p->fun6(); /* 同上,由于b类未改写需函数fun6(),所以该处调用,实际调用a::fun6(),编译器做如下处理: p->fun6() 变为 p->vptr[1],由于,fun6为第二声明,此时,offset为1 */ p = &var2; p->fun6();//调用c::fun6() 虚表: 1。索引;2。函数指针(这值在子类重写虚函数后,发生相应变化) 将p->fun1() 替换为:p->vptr[offset], vptr名字为虚表指针: offset值随虚函数声明次序而定,如果第一个声明,索引则为0,出现在虚表的第一项,第二个声明则为1,以次。。。(此时offset 为 0 即是:p->vptr[0]) p为基类,可指向子类的任何对象 (附加: 1。类型转换实际是:内存切割,管辖内存从大变小,从大变小,现实世界中,是允许,在C++中,也是允许的;编译器不允许基类对象向子类对象的转化,因为从小变大,会导致内存访问越界。 2。指针的类型,实际上决定了通过该指针能访问的内存范围 ), 所指向对象的类型不同,vptr不同 */ p->fun6(); /* 同上,由于b类未改写需函数fun6(),所以该处调用,实际调用a::fun6(),编译器做如下处理: p->fun6() 变为 p->vptr[1],由于,fun6为第二声明,此时,offset为1 */ p = &var2; p->fun6();//调用c::fun6() 虚表: 1。索引;2。函数指针(这值在子类重写虚函数后,发生相应变化) 将p->fun1() 替换为:p->vptr[offset], vptr名字为虚表指针: offset值随虚函数声明次序而定,如果第一个声明,索引则为0,出现在虚表的第一项,第二个声明则为1,以次。。。(此时offset 为 0 即是:p->vptr[0]) p为基类,可指向子类的任何对象 (附加: 1。类型转换实际是:内存切割,管辖内存从大变小,从大变小,现实世界中,是允许,在C++中,也是允许的;编译器不允许基类对象向子类对象的转化,因为从小变大,会导致内存访问越界。 2。指针的类型,实际上决定了通过该指针能访问的内存范围 ), 所指向对象的类型不同,vptr不同 */ p->fun6(); /* 同上,由于b类未改写需函数fun6(),所以该处调用,实际调用a::fun6(),编译器做如下处理: p->fun6() 变为 p->vptr[1],由于,fun6为第二声明,此时,offset为1 */ p = &var2; p->fun6();//调用c::fun6() |
-- 作者:enorm -- 发布时间:12/15/2005 12:11:00 PM -- 自己顶一下~~~ |
-- 作者:enorm -- 发布时间:12/15/2005 12:12:00 PM -- 自己顶一下~~~ |
W 3 C h i n a ( since 2003 ) 旗 下 站 点 苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》 |
77.637ms |