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

2018-08-21 16:57:25 +08:00
 HackerPainter
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输出的值不一样?

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

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

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

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

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

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

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

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

多少个 ACM 出身的同学都是因为写炫技代码被 leader 拍死的。。。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/481897

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX