C++ 内存分布,编译与运行阶段探索?(暂时不确定标题)

前接Virtual机制,说到多重继承,虚函数是能完美兼容的,但是非虚函数不能,并且出现了有趣的过程,这里来探讨一下。

多重派生时,如果有非虚的重名同参数列表函数,就不能完美继承了,并且还可能产生隐性事故?

代码如下:

#include<iostream>
#include<stdio.h>
using namespace std;
class A{
public:
        virtual ~A(){cout <<"A destruction"<<endl;}
        virtual void func(){cout <<"A func."<<endl;}
};
class A2{
public:
        virtual ~A2(){cout <<"A2 destruction"<<endl;}
        virtual void func(){cout <<"A2 func."<<endl;}
        void func2(){cout << "A2 func2."<<endl;}
};
class B{
public:
        virtual ~B(){cout <<"B destruction"<<endl;}
        virtual void func(){cout<<"B func."<<endl;}
};

class C:public A,public B
{
public:
        virtual ~C(){cout <<"C destruction"<<endl;}
        virtual void func(){cout <<"C func."<<endl;}
};
class D:public C
{
public:
        virtual ~D(){cout<<"D destruction"<<endl;}
        virtual void func(){cout<<"C func."<<endl;}
        void func2(){cout << "D func2."<<endl;}
};
class E:public A2,public D
{
public:
        virtual ~E(){cout <<"E destruction"<<endl;}
        void fooE(){}
        virtual void func(){cout <<"E func."<<endl;}
//      void func2(){cout << "E func2."<<endl;}
};
int main(){
        A a,a2;
        A2 a2222;
        B b,b2;
        C c,c2;
        D d,d2;
        E e,e2;
 //       e.func2();
        d.func2();
        a2222.func2();


        return 0;
}
注意,这个时候是能编译成功的!!!

但是如果把e.func2()的注释放开,就编译不成了。

# g++ non_virtual_multi.cpp 
non_virtual_multi.cpp: In function ‘int main()’:
non_virtual_multi.cpp:50: 错误:对成员‘func2’的请求有歧义
non_virtual_multi.cpp:33: 错误:candidates are: void D::func2()
non_virtual_multi.cpp:14: 错误:                void A2::func2()

这个感觉就有点像是从一个空指针里要数据,默认空指针是没事的,表达式也没事,但是如果当左值,要地址,就崩溃core dumped。(注意,是运行到那步才崩溃,我就见过奇葩笔试题,你不能说崩溃,你得把前边运行的打印也写出去,然后崩溃~~)

函数也是,我真要调用才发现问题(但还不是运行时,刚刚把调用语句写到代码里,编译就过不去了)


那么这点就很有趣了,同样是编译,写出调用和不写出调用两个样!

而同样是这种找不到要访问目标的错误,一种(访问函数)是编译错,另一种(去空地址取值)却是运行错。


关于函数入口地址:前边虚函数表的图画到了虚指针指向虚表,虚表存储函数入口,然后就能让对象找到相应的实现了,比如是这种?A::func() B::func

那么这些函数是怎么存放的,或者说怎么调用的?只记得有专门的数据段、代码段等,代码段肯定是加载到内存了(或是换出到虚拟内存了?)只要想要调用函数,就给出函数的地址,然后把代码段(可能换页调入内存)运行。


编译原理没怎么学,只能瞎推测一下:

这个多重继承,派生类总得给func2()找一个目标函数地址用于执行的,绑定谁却没个依据。但是编译成功了,所以不是在编译阶段就绑定的func2()。确切的说,不是类的声明定义阶段。

既然是出现调用语句才提示请求起义,似乎这种绑定也未发生在对象的声明与定义阶段。

抑或是,纯粹的编译器优化,编译器本来就解释不了这个歧义。不调用就认为没有风险,不提示歧义,有调用就提示歧义。这样解释更靠谱点,毕竟都是编译阶段嘛,感觉用阶段论有点站不住脚,至少我解释不太好。

不能对象自己调用歧义函数,那如果用类指针去调用呢?也不会编译错,运行也没问题,因为非虚函数,基类指针指向派生类对象,再去调用该函数,指的是找基类的func2(),这个没歧义,没法这么测。反过来测呢,派生类又不允许指向基类对象,没办法。


而关于访问空指针内容的崩溃错误。应该是取空地址是到那一步才会错,至于为什么不在编译阶段指针ptr赋值NULL时就跟踪它,发现有它再去调用func()之类的成员的代码就让它编译错,和这个多重继承一样处理呢?估计是不好跟踪,你又不能一开始就禁止赋值NULL,然后后续ptr变来变去,可能编译器办不到这个事,另外,这里边也有动态的,编译时就不知道的东西,比如你要在程序运行时手动输入一个值0,然后指针就赋值成这个值了。编译器无法未卜先知。而类的相关函数入口地址,却是应该在编译阶段就绑定的。


今天就先到这了,累,没思路。有空各种内存分布,代码、数据,高低地址,各种过程,再巩固一下。把内存分布这个地方的知识点再综合一下,总结一下。

相关问题比如代码段是先加载到内存,还是扔到硬盘缓存,运行的时候再换页?这个虽然可能是操作系统控制的,一视同仁,但是其实如果能优化,是有好处的,所以可能可以设置。





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值