在C++中转换函数指针时的奇怪行为

dba*_*ric 16 c++ pointers casting function-pointers

我最近在C++中遇到了关于函数指针的行为,我无法完全理解.我向谷歌寻求帮助以及一些经验丰富的同事,但即使他们也无能为力.

以下代码展示了这种神秘行为:

class MyClass{
private:
    int i;

public:
    MyClass(): i(0) {}
    MyClass(int i): i(i) {}

    void PrintText() const { std::cout << "some text " << std::endl;}
};

typedef void (*MyFunction) (void*);

void func(MyClass& mc){
    mc.PrintText();
}

int main(){    
    void* v_mc = new MyClass;
    MyFunction f = (MyFunction) func; //It works!
    f(v_mc); //It works correctly!!!

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

所以,首先我定义一个稍后将使用的简单类(特别是它的成员方法PrintText).然后,我将名称对象定义void (*) (void*)MyFunction- 指向具有一个void*参数且不返回值的函数的指针.

之后,我定义func()了接受MyClass对象引用并调用其方法的函数PrintText.

最后,魔术发生在主要功能上.我为MyClass返回指针的新对象动态分配内存void*.然后,我将指向func()函数的指针转换为MyFunction指针 - 我没想到这会编译,但确实如此.

最后,即使底层函数()接受对象的引用,我也会用void*参数调用这个新对象.一切正常!func()MyClass

我尝试使用Visual Studio 2010(Windows)和XCode 5(OSX)编译此代码,它以相同的方式工作 - 不报告任何警告.我想这就是为什么它的工作原理是C++引用实际上是作为幕后指针实现的,但这不是解释.

我希望有人可以解释这种行为.

Rei*_*ica 31

形式化的解释很简单:未定义的行为未定义.当您通过一个指针函数类型调用一个函数,它是不确定的行为和程序可以合法地做任何事情(崩溃,表现为运行状态,为了比萨饼在线...点儿去).

您可以尝试推理您遇到的行为发生的原因.它可能是以下一个或多个因素的组合:

  • 您的编译器在内部将引用实现为指针.
  • 在您的平台上,所有指针都具有相同的大小和二进制表示.
  • 由于PrintText()根本不访问*this,编译器可以有效地忽略mc完全的值,只需调用PrintText()里面的函数func.

但是,你必须记住,当你正在经历你在当前平台,编译器版本和月球这个阶段所描述的行为时,这可能在任何时候都没有明显的原因(例如改变)周围代码触发不同的优化).请记住,未定义的行为是未定义的.


至于为什么你可以强制&func转换MyFunction- 标准明确允许(使用a reinterpret_cast,C风格的演员在此上下文中转换).您可以合法地将指向函数的指针强制转换为任何其他指向函数类型的指针.但是,你可以合法地做的唯一事情就是移动它或将其转换回原始类型.如上所述,如果通过错误类型的函数指针调用,则它是未定义的行为.

  • mmhm,我不介意编写一些不好的代码,如果编译器可以说服我为我订购披萨. (10认同)

eer*_*ika 7

我希望有人可以解释这种行为.

行为未定义.

MyFunction f = (MyFunction) func; //It works!
Run Code Online (Sandbox Code Playgroud)

它"有效",因为你使用的是c风格的演员,reinterpret_cast我认为它具有与这种情况相同的效果.如果您曾经使用过static_cast或者根本就没有使用,那么编译器会警告您的错误并且失败了.当您调用错误解释的函数指针时,您将获得未定义的行为.