我最近在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;
}
所以,首先我定义一个稍后将使用的简单类(特别是它的成员方法PrintText
)。然后,我将名称对象void (*) (void*)
定义为MyFunction
- 指向函数的指针,该函数具有一个void*
参数且不返回值。
之后,我定义函数func()
接受对MyClass
对象的引用并调用其方法PrintText
。
最后,魔术发生在主要功能上。我为新MyClass
对象动态分配内存,将返回的指针转换为void*
。然后,我将指向func()
函数的指针转换为MyFunction
指针 - 我根本没想到它会编译,但确实如此。
最后,即使基础函数(void*
)接受对func()
对象的引用,我也会使用 MyClass
参数调用此新对象。一切正常!
我尝试使用Visual Studio 2010(Windows)和XCode 5(OSX)编译此代码,它的工作方式相同 - 不会报告任何警告。我想这可行的原因是C ++引用实际上是作为幕后指针实现的,但这不是解释。
我希望有人可以解释这种行为。
答案 0 :(得分:31)
正式的解释很简单:未定义的行为未定义。当您通过指向不同函数类型的指针调用函数时,它的未定义行为和程序可以合法地执行任何操作(崩溃,似乎工作,在线订购披萨......任何事情都去。)
您可以尝试推理您遇到的行为发生的原因。它可能是这些因素中的一个或多个的组合:
PrintText()
根本无法访问*this
,编译器可以完全有效地忽略mc
的值,只需调用PrintText()
内的func
函数1}}。但是,您必须记住,当您目前正在体验您在当前平台,编译器版本以及月球这个阶段所描述的行为时,这可能会随时发生变化而无明显原因无论如何(例如周围代码的变化触发不同的优化)。请记住,未定义的行为是未定义的。
至于为什么可以将&func
强制转换为MyFunction
- 标准明确允许(使用reinterpret_cast
,C风格的演员在此上下文中转换)。您可以合法地将指向函数的指针强制转换为任何其他指向函数类型的指针。但是,你可以合法地做的唯一事情就是移动它或将其转换回原始类型。如上所述,如果你通过错误类型的函数指针调用它,那就是它的未定义行为。
答案 1 :(得分:7)
我希望有人可以解释这种行为。
行为未定义。
MyFunction f = (MyFunction) func; //It works!
它“有效”,因为你使用的是c风格的演员,我认为在这种情况下效果与reinterpret_cast
相同。如果你曾经使用static_cast
或者根本就没有使用,那么编译器会警告你的错误并且失败了。当您调用错误解释的函数指针时,您将获得未定义的行为。
答案 2 :(得分:2)
只有偶然的机会才有效。编译器不保证能够正常工作。在幕后,您的编译器将引用视为指针,因此您的替代函数签名恰好可以正常工作。
答案 3 :(得分:0)
对不起,对我来说并不清楚为什么你称这是一种奇怪的行为,我没有看到这里依赖于月亮周期的未定义行为,是使用函数指针的方法在C.
添加一些调试输出,您可能会看到指向对象的指针在所有调用中保持不变。
void PrintText() const { std::cout << "some text " << this << std::endl;}
^^^^
void func(MyClass& mc){
std::cout << (void *)&mc << std::endl;
^^^
void *v_mc = new MyClass;
std::cout << (void *)v_mc << std::endl;
^^^^