首页   注册   登录
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
拉勾
V2EX  ›  C/C++/Obj-C

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

  •  
  •   HackerPainter · 119 天前 · 1588 次点击
    这是一个创建于 119 天前的主题,其中的信息可能已经有所发展或是发生改变。
    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
        1
    phttc   119 天前
    我拿来跑了一下,输出一样的。。
        2
    HackerPainter   119 天前
    @phttc 你用的 32 位机器吧,64 位机器是不一样的,我用 linux 服务器和 mac 都试过了,是不一样的
        3
    cgsv   119 天前
    typedef void (*PFun)(void* self);
    PFun fun = (PFun)((long *)*((long *)*(long*)(pD)));
    fun(pD);
        4
    HarveyDent   119 天前   ♥ 1
    你这个是未定义行为啊,不同编译器肯定不同。

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

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

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

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

    @HackerPainter #13 请你确保你是否在使用 COM,因为 COM 规定了接口方法必须以某种方式实现,那样才能确保这样的代码是可以工作的(在第一个参数放了 this 之后)。如果只是随便一个 C++ 的虚函数,这样做无法保证有任何好下场。
        16
    eastera   119 天前
    看编译器怎么做的,虚函数表没有要求,不同编译器结果可能不一样
        17
    lychnis   119 天前
    上面解释的很清楚了 这种代码绝对不允许出现在 svn 里面 只能自己玩
        18
    bilosikia   118 天前
    #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 的吗
        19
    qinyusen   118 天前
    工作中直接打死+1

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

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

    多少个 ACM 出身的同学都是因为写炫技代码被 leader 拍死的。。。
    关于   ·   FAQ   ·   API   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   822 人在线   最高记录 4019   ·  
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.1 · 31ms · UTC 19:46 · PVG 03:46 · LAX 11:46 · JFK 14:46
    ♥ Do have faith in what you're doing.
    沪ICP备16043287号-1