强制转换

来源:互联网 发布:尤肖虎 院士 知乎 编辑:程序博客网 时间:2024/04/27 23:48

 

class A{
public:
        void print1(){
                printf("1/n");
        }
        virtual void print2(){
                printf("2/n");
        }
};

class B : public A{
public:
        void print1(){
                printf("3/n", s);
        }
        virtual void print2(){
                printf("4/n", s);
        }
};

void print(){
    A* ap = new A();
    ap->print1();
    ap->print2();

    delete ap;
    ap = new B();
    ap->print1();
    ap->print2();

    delete ap;
    B* bp = (B*)new A();
    bp->print1();
    bp->print2();
}
以上程序的输出为:
1
2
1
4
3
2
这里面最后这个B* bp = (B*)new A();我是一点都不理解, 我原以为程序在这里报异常, 谁知却可以运行出让人目瞪口呆的结果出来.
我之所以有将报异常的印象, 是因为<<more effective c++>>上说专用于类类别转换的dynamic_cust会自动判断dynamic_cust是否成功, 对于指针来说如果结果是不能转换, 则指针变为NULL, 对于&引用则会抛异常出来.  而(B*)与dynamic<B*>() 的作用其实是相同的, 所以我以为二者会有相同的外在表现.

而实际上(B*)这种C语言的强制转换是缺乏这种是否可转的检查的, 因此程序能运行出结果出来, 但实际上这种结果只是全部可能的异常情况中看起来最正常的一种, 该段程序其它的可能包括导致程序崩溃等等.
后来我把那一段强制转换改成 :B* bp = dynamic_cast<B*>(new A());  之后, 发现程序运行时报野指针错误, "非法的对0x00000000的访问". 然后我在强制转换后加了一个判断
if(bp == NULL){
    printf("cust not allowed!/n" );
}
这才明显地看出来, dynamic_cast确实把该指针批向NULL了.   也就是说, 考试题中的代码实际上是不合法的.

另外我又更进一步做了如下的试验,
class C{
public:
        void print1(){
                printf("5/n");
        }
        virtual void print2(){
                printf("6/n");
        }
};

C* pc = (C*) new A();    //这种写法比考试题更无耻
pc->print1();
pc->print2();
程序居然还可以运行, 而且结果输出为:
5
2
唉, 看到这种结果我真是觉得有些无法解释. 应该是跟指向函数表的vptr虚函数指针有关系 <<inside C++ object model>>中对此有深入描述, 看来确实有必要读一下这本书了.......

20080707 注释:看过more effective c++ item24之后,对最后这个问题又有了新的理解。这种对指针的转型B* bp = (B*) new A(); 对于这当前的程序来说,完全是可以正常运行的,但也属于打擦边球,如果B对象中多一个 int i; 那么这个程序运行的结果就不可预测了。
       先说为什么现成这个程序可以正常运行。对于B来说print1非virtual函数,则编译器会直接根据指针bp的类型B*将其与B::print1() 连接起来。而对于print2, 则会根据vptr 虚函数指针去虚函数表去寻找准确的函数,然后动态绑定时发现bp所指的对象其实是A,因此就把bp->print2()指向对 A::print2()的调用。对于后台的与A完全无关的C类也可以强制转换并调用,其实道理也是完全一样的。
        再说为什么随遍添加几个类成员就可能让程序运行结果不可预测。A* ap = new A(), 导致在堆上分配了4个字节,这四个字节存储了vptr。而如果B中有一个int成员变量的话,则sizeof(B)=8, 而B* bp = (B*) ap导致进行了一个强制转换,指向堆上4个字节A对象的指针被强制标记为指向堆上8个字节B对象的指针。这样一来,程序就把4个字节上方4个节也也私自(相对于光明正大的new 或者malloc操作)据为已有,当程序试图操作这本不属于他的4个字节时,其结果是不可预料的。另外当前这个程序能否正常运行也依赖于编译器的实现,这个“非法的“程序由于不符合C++语法,因此无法保证它可以在全部依照C++标准而实现的编译器下编译并运行。从dynamic_cast操作的结果来看,C++对于此种强制转换也是拒绝的,因为cast的结果是一个NULL指针。

原创粉丝点击