V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
HackerPainter
V2EX  ›  C

求助 C++大神看一个问题

  •  
  •   HackerPainter · 2018-08-21 16:57:25 +08:00 · 3945 次点击
    这是一个创建于 2284 天前的主题,其中的信息可能已经有所发展或是发生改变。
    class B {
    public:
        virtual void foo() {}
    };
    
    class D: public B {
    public:
        D() : mA(0) {}
        virtual void foo() {
            cout<<"D::foo::mA "<<mA<<endl;
        }
        int mA;
    };
    
    int main() {
        D d1;
        D* pD = &d1;
        cout<<pD<<endl;
        typedef void (*PFun)();
        PFun fun =  (PFun)((long *)*((long *)*(long*)(pD)));
        fun();
        cout<<"D::pD::mA: "<<pD->mA<<endl;
    }
    

    为啥mA输出的值不一样?

    20 条回复    2018-08-22 12:22:24 +08:00
    phttc
        1
    phttc  
       2018-08-21 17:21:28 +08:00
    我拿来跑了一下,输出一样的。。
    HackerPainter
        2
    HackerPainter  
    OP
       2018-08-21 17:27:41 +08:00
    @phttc 你用的 32 位机器吧,64 位机器是不一样的,我用 linux 服务器和 mac 都试过了,是不一样的
    cgsv
        3
    cgsv  
       2018-08-21 17:30:07 +08:00
    typedef void (*PFun)(void* self);
    PFun fun = (PFun)((long *)*((long *)*(long*)(pD)));
    fun(pD);
    HarveyDent
        4
    HarveyDent  
       2018-08-21 17:30:53 +08:00   ❤️ 1
    你这个是未定义行为啊,不同编译器肯定不同。

    如果你非要这么干的话,类成员函数应该是需要一个 this 指针的,这样改一下在我的环境 gcc 能得到一样的结果了:
    typedef void (*PFun)(D* p);
    PFun fun = (PFun)((long *)*((long *)*(long*)(pD)));
    fun(pD);

    同样的环境:
    fun(0);
    我获得了一个 Segmentation fault,也是符合预期的。

    如果你想探究一下编译器怎么实现虚函数表的,可以试着玩一下。
    wevsty
        5
    wevsty  
       2018-08-21 17:32:04 +08:00
    我十分想请楼主解释一下这行是啥意思。
    PFun fun = (PFun)((long *)*((long *)*(long*)(pD)));
    pD 是一个指向 class D 的指针,pD 转换为一个 long 型的指针以后再对他解引用是个什么操作?解引用出来的东西又强制解释为指针第二次解引用,最后还要转换成函数指针又是个什么操作,看不懂。
    HackerPainter
        6
    HackerPainter  
    OP
       2018-08-21 17:33:13 +08:00
    @cgsv 能说明一下原因吗?我只知道 c++成员函数扩展后第一个参数是对象 this,不知道函数指针 PFun 带参数与这有啥关系
    HackerPainter
        7
    HackerPainter  
    OP
       2018-08-21 17:35:09 +08:00
    @wevsty 直接通过虚函数表指针调用函数
    HackerPainter
        8
    HackerPainter  
    OP
       2018-08-21 17:37:03 +08:00
    @HarveyDent 明白了,thks
    wevsty
        9
    wevsty  
       2018-08-21 17:43:13 +08:00   ❤️ 1
    @HackerPainter
    虚函数表是编译器决定怎么实现的,这样子不能保证行为。
    如果要调用类的成员函数,即使成员函数不需要参数,成员函数的第一个参数仍然 this 指针,并不是空参数。

    在 MSVC X64 的编译器下面,你这代码附带一个编译警告 C4312,运行直接崩。
    原因是 MSVC X64 的 long 是 32 位的,而 long*是 64 位的。
    gnaggnoyil
        10
    gnaggnoyil  
       2018-08-21 18:04:52 +08:00
    * `void ()`
    * `void (D::)()`
    * `long`
    这三个类型之间两两相互不 type aliasing/pointer interchangeable,LZ 你自己数数自己触发了多少未定义行为……
    HackerPainter
        11
    HackerPainter  
    OP
       2018-08-21 18:45:52 +08:00
    @gnaggnoyil 没有触发,gcc 都能正常编译
    yanxijian
        12
    yanxijian  
       2018-08-21 19:01:10 +08:00 via iPhone
    磨练技术也不用写这种代码吧。工作中遇到直接打死😏
    HackerPainter
        13
    HackerPainter  
    OP
       2018-08-21 19:32:17 +08:00
    @yanxijian 工作中一些大神将函数指针用的神乎其技,没办法
    GeruzoniAnsasu
        14
    GeruzoniAnsasu  
       2018-08-21 20:00:12 +08:00
    geelaw
        15
    geelaw  
       2018-08-21 20:05:56 +08:00
    有些编译器实现的虚函数指针的长度是普通函数指针的两倍,似乎有虚拟继承的原因。不要这么做。

    @HackerPainter #13 请你确保你是否在使用 COM,因为 COM 规定了接口方法必须以某种方式实现,那样才能确保这样的代码是可以工作的(在第一个参数放了 this 之后)。如果只是随便一个 C++ 的虚函数,这样做无法保证有任何好下场。
    eastera
        16
    eastera  
       2018-08-21 22:24:09 +08:00
    看编译器怎么做的,虚函数表没有要求,不同编译器结果可能不一样
    lychnis
        17
    lychnis  
       2018-08-22 01:52:55 +08:00
    上面解释的很清楚了 这种代码绝对不允许出现在 svn 里面 只能自己玩
    bilosikia
        18
    bilosikia  
       2018-08-22 10:16:52 +08:00
    #include <iostream>
    using namespace std;
    class B {
    public:
    virtual void foo() {}
    };

    class D: public B {
    public:
    D() : mA(888) {}
    virtual void foo() {
    cout<<"D::foo::mA "<<mA<<endl;
    }
    int mA;
    };

    int main() {
    D d1;
    D* pD = &d1;
    cout<<pD<<endl;
    typedef void (*PFun)(D *a);
    PFun fun = (PFun)((long *)*((long *)*(long*)(pD)));
    fun(pD);
    cout<<"D::pD::mA: "<<pD->mA<<endl;
    }

    这样就是一样的了, 你不需要传 this 的吗
    qinyusen
        19
    qinyusen  
       2018-08-22 12:19:13 +08:00
    工作中直接打死+1

    如果是日常爱好,请使用 GDB 单步调试,看一下所有的地址就好了。
    qinyusen
        20
    qinyusen  
       2018-08-22 12:22:24 +08:00
    @HackerPainter 工作中的大神,应该写的是谁都能维护的“蠢”代码,但是一样结构合理思路清晰。

    你这是炫技的大神,工作中,这种 code 因为可维护性为 0,除非是需要极致性能的情况下,否则,就是一棒子打死,reviewer 会勒令整改的。

    多少个 ACM 出身的同学都是因为写炫技代码被 leader 拍死的。。。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3505 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 32ms · UTC 00:49 · PVG 08:49 · LAX 16:49 · JFK 19:49
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.