注册 登录
编程论坛 C++教室

强转指针导致虚函数表丢失?

fg5823820 发布于 2012-01-03 21:54, 2160 次点击
百度了很多关于虚函数表的文章,都讲解了虚函数表的原理,看得一知半解,目前没法解决我遇到的问题:
程序代码:

#include <string>
#include <iostream>
using namespace std;

struct A
{
    virtual void FunA() = 0;
};

struct C : public A
{
    C():num(1000){}
    virtual void FunA()
    {
        cout<<"FunA:"<<num*num<<endl;
    }

    long num;
};

int _tmain(int argc, _TCHAR* argv[])
{
    C* c = new C;
    A* a;
    void* p = c;
    a = (A*)p;
    a->FunA();
    delete c;
    return 0;
}
打印的时候开起来没有任何错误,不过调试就会发现虚表是错误的。

我最困惑的地方是:
1、有时候这样转换是没有问题的,有时候就会出问题。
2、虚表到底是保存在什么位置?为什么转成别的类型指针后虚表会丢失?
12 回复
#2
鑫乐源2012-01-03 23:35
解释起来太麻烦,涉及C++对象内存布局
下面的这个链接可以解释一部分,粗略的
https://hi.bccn.net/space-459307-do-blog-id-32857.html
#3
rjsp2012-01-04 08:21
代码没问题,又不是多重继承

除了应该将 int _tmain(int argc, _TCHAR* argv[])
改为 int main(int argc, char* argv[]) 之外
不要将某个编译器特有的东西带到C++中来
#4
fg58238202012-01-04 09:55
回复 3楼 rjsp
改成int main并再次尝试了,这是结果
[IMG]http://img165.[/IMG]
#5
fg58238202012-01-04 10:01
回复 2楼 鑫乐源
其实我正是遇到与那个帖子相同的问题,最初遇到问题的时候就是在多继承的时候,后来去试了一下,并非只有在多继承的时候才会发生。

我给出的代码里原来还有个B接口,C同时继承A和B,总是有一个接口强转失败(A接口),刚开始认为是继承顺序导致,后来果断去掉B接口,只继承A接口,发现一样强转失败,目前能解决这个问题的唯一方法,就是将void*先转成C*,再转成任何C继承任何父类,更复杂的情况没有去尝试。虚表这东西还真搞不懂其实我正是遇到与那个帖子相同的问题,最初遇到问题的时候就是在多继承的时候,后来去试了一下,并非只有在多继承的时候才会发生。

我给出的代码里原来还有个B接口,C同时继承A和B,总是有一个接口强转失败(A接口),刚开始认为是继承顺序导致,后来果断去掉B接口,只继承A接口,发现一样强转失败,目前能解决这个问题的唯一方法,就是将void*先转成C*,再转成任何C继承任何父类,更复杂的情况没有去尝试。虚表这东西还真搞不懂
#6
鑫乐源2012-01-05 23:36
似乎在哪里看到过说,不同的c++编译器出来的对象内存布局可能也不同,主要是虚函数表这东西的位置。如果你知道void*之后可能转换成的类型,那么在void*之前应该传递该类型的基类类型,比如 void* p = (A*)c;,一般不会出错,你自己验证一下吧
#7
fg58238202012-01-06 09:41
回复 6楼 鑫乐源
的确这样虚表就不会错了,非常感谢,试了多继承也没问题,看来虚表这东西要好好研究研究
#8
flyxkj2012-02-01 10:50
编译器用的什么哦!用g++编译器不会有任何问题!
#9
湿柴2012-02-01 13:07
看不懂啊···
#10
fg58238202012-02-27 16:24
回复 8楼 flyxkj
你可以试试这段代码,看看打印结果
程序代码:

struct A
{
    virtual void FunA() = 0;
};

struct B
{
    virtual void FunB() = 0;
};

struct C : public A, public B
{
    C():num(1000){}
    virtual void FunA()
    {
        cout<<"FunA:"<<num*num<<endl;
    }

    virtual void FunB()
    {
        cout<<"FunB:"<<num*num<<endl;
    }

    long num;
};

int main()
{
    C* c = new C;
    A* a;
    void* p = c;
    a = (A*)p;
    a->FunA();

    B* b;
    b = (B*)p;
    b->FunB();


    delete c;
    return 0;
}
#11
pangding2012-02-28 02:09
函数表是全类共享一个,不是一个对象一个。所以才需要用 this 指针来获得当前调用函数的对象究竟是哪个。
另外这种实现方式也不是标准规定的,所以编译器可以自己发挥。

你自己也提了,正确的做法肯定是
A *a = &c

像你这样类型转换是有大问题的。考虑下面的代码:
程序代码:
class A {};
class B : public A {};
class C : public A {};
class D : public B, public C {};

int main()
{
    D d;
    A* pa = &d;

    return 0;
}

它是有语法错误。
但如果你试图用 void * 从中迂回,那么就可以避免语法错误。

继承,包括虚继承,编译器都会为类型做很多检查。也会为动态绑定做很多准备,众所周知,多态是基于类型。不是一个地址就能获取全部的信息。
强制转换就等干在告诉编译器“请按我的想法来做”。编译器虽然不知道你要做什么,但是还是会遵从你的指示。

我觉得你这应该是一种未定义的行为,和 ++a + ++a 之类的代码差不多。但我找了半天,也没在标准上找到相关的规定。不过问题挺有意思的,我以前也没怎么考虑过。
#12
pangding2012-02-28 02:15
哦。顺带一提,我刚才举的那个例子的问题,可以用虚继承解决。一般引入虚继承的时而都是举的这个例子。
不过我觉得  C++ 的这几个机制不是很有意义。实践普遍认为应该尽量减少多继承(甚至是严格禁止使用),因为它是万恶之源。
#13
小鱼儿c2012-02-28 20:50
这个你推你推荐看<精通MFC> 刘晓华 写的。
c++的地方 绝对可以解惑你这个问题。。

不管你怎么转换虚函数表是不可以复制。(类对象拷贝的时候)和他是一个类共享的。
每个类的前4个字节保存的虚函数表的地址。。

你可以取出来然后使用。。。。
那本书就写相当好。。

1