求助 C++大神看一个问题 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
HackerPainter
V2EX    C

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

  •  
  •   HackerPainter 2018-08-21 16:57:25 +08:00 4452 次点击
    这是一个创建于 2675 天前的主题,其中的信息可能已经有所发展或是发生改变。
    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 工作中一些大神将函数指针用的神乎其技,没办法
    td width="auto" valign="top" align="left">
        14
    GeruzoniAnsasu  
       2018-08-21 20:00:12 +08:00
    GeruzoniAnsasu
    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     Solana     891 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 26ms UTC 21:27 PVG 05:27 LAX 13:27 JFK 16:27
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86