所以在" c ++编程语言,第4版"中,有一段我不了解指针到函数类型的转换。以下是一些代码示例。
using P1 = int(*)(int*);
using P2 = void(*)(void);
void f(P1 pf) {
P2 pf2 = reinterpret_cast<P2>(pf);
pf2(); // likely serious problem
// other codes
}
当我跑这个时它崩溃了。
我不确定我是否正确,但我最初认为可能是严重的问题&#34;注释是当pf在pf2中被转换为P2时,我认为pf2没有指向任何东西?因为当我创建一个匹配P2&type;并将pf2指向它的函数时,它并没有崩溃并且正常运行。
在代码之后,我读到了这个:
我们需要最糟糕的强制转换,reinterpret_cast,来转换指针到函数类型。原因是使用指向错误类型函数的指针的结果是不可预测的和系统相关的。例如,在上面的示例中,被调用函数可以写入其参数指向的对象,但调用pf2()不提供任何参数!
现在我从&#34开始完全迷失了;例如,在上面的示例中&#34;部分:
我想我在这里遗漏了一些东西。有人可以解释一下吗?
答案 0 :(得分:2)
例如,在上面的例子中,被调用的函数可以写入由其参数指向的对象(...)
pf
是指向这样一个函数的指针:
int foo(int* intPtr)
{
// ...
}
因此可以实现写入其参数:
int foo(int* intPtr)
{
*intPtr = 42; // writing to the address given as argument
return 0;
}
(...)但是调用pf2()没有提供任何参数!
当您通过其强制转换foo
来调用P2
时,它将被调用而不带参数,因此不清楚intPtr
将是什么:
P2 pf2 = reinterpret_cast<P2>(pf);
pf2(); // no argument given here, although pf2 really is foo() and expects one!
写它很可能会破坏某些东西。
此外,编译器通常首先通过为返回值保留空间来实现对返回某些东西的函数的调用,然后由函数调用填充。当您使用P1
的签名呼叫P2
时,对P2
的呼叫不会保留空间(因为返回值为void
)和实际呼叫将在不应该的地方写一个int
,这是腐败的另一个来源。
答案 1 :(得分:0)
现在我完全迷失了“例如,在示例中 以上“部分:
“可以写入其参数指向的对象”//对象是什么 它到底是什么?
P1是一个期望非const指针到int参数的函数。这意味着它可以写入其参数中引用的int。
“但是调用pf2()没有提供任何参数!” //“使用P2 = void(*)(void);“真的不需要辩论吗?
当你通过另一个没有参数的函数指针类型调用函数时,不满足被调用函数的期望。它可能会尝试将堆栈中的任何内容解释为int指针并写入它,从而导致未定义的行为。
答案 2 :(得分:0)
这确实失败了,但不一定是人们可能期望的方式。
函数指针的实现留给编译器(未定义)。甚至函数指针的大小也可能大于void *。
What is guaranteed about the size of a function pointer?
在函数指针的值中没有关于任何内容的保证。事实上,唯一的保证,比较运算符将在相同类型的函数指针之间工作。
标准确实提供了函数指针可以存储其他函数类型的值。
将函数指针转换为另一种类型的未定义行为,这意味着编译器可以做任何想做的事情。您是否提供参数确实无关紧要,以及失败的方式取决于系统的调用约定。就你所关心而言,它可能让“恶魔从你的鼻子中飞出来”。
Casting a function pointer to another type
因此,我们回到了作者的陈述:
我们需要最糟糕的强制转换,reinterpret_cast,来转换指针到函数类型。原因是使用指向错误类型函数的指针的结果是不可预测的和系统相关的。例如,在上面的示例中,被调用函数可以写入其参数指向的对象,但调用pf2()不提供任何参数!
这是试图指出,如果没有指定参数,如果函数写入输出,它将写入某个未初始化状态。基本上,如果你把这个功能视为
int foo(int* arg) {*arg=10;}
如果你没有初始化arg,作者说你可以在任何地方写作。但同样,没有任何保证,这甚至是重要的。系统可以将脚印int (*)(int*)
和void(*)(void)
存储在完全不同的内存空间中,在这种情况下,您可以跳转到随机位置而不是上述问题在该计划中。未定义的行为就是:undefined。
就是不要这样做。