我试图运行以下程序,但得到一些奇怪的错误:
档案1.c:
typedef unsigned long (*FN_GET_VAL)(void);
FN_GET_VAL gfnPtr;
void setCallback(const void *fnPointer)
{
gfnPtr = *((FN_GET_VAL*) (&fnPointer));
}
档案2.c:
extern FN_GET_VAL gfnPtr;
unsigned long myfunc(void)
{
return 0;
}
main()
{
setCallback((void*)myfunc);
gfnPtr(); /* Crashing as value was not properly
assigned in setCallback function */
}
这里使用gcc编译时,gfnPtr()在64位suse linux上崩溃。但它成功调用了gfnPtr()VC6和SunOS。
但如果我改变下面给出的功能,它就能成功运行。
void setCallback(const void *fnPointer)
{
int i; // put any statement here
gfnPtr = *((FN_GET_VAL*) (&fnPointer));
}
请帮助解决问题的原因。谢谢。
C标准不允许将函数指针强制转换为void*
。您可能只会转换为另一个函数指针类型。在C11 standard, 6.3.2.3 §8:
指向一种类型的函数的指针可以被转换为指向另一种类型的函数的指针并且再次返回
重要的是,在使用指针调用函数之前必须强制转换回原始类型(技术上,为兼容类型。在6.2.7中定义“compatible”)。
请注意,由于使用它们的上下文,许多(但不是全部)C编译器必须遵循的POSIX标准要求将函数指针转换为void*
并返回。这对于某些系统功能(例如dlsym
)是必需的。
遗憾的是,标准不允许在数据指针和函数指针之间进行转换(因为这可能在一些非常模糊的平台上没有意义),即使POSIX和其他人需要这样的转换。一种解决方法是不转换指针,而是转换指针指针(编译器可以这样做,它将在所有普通平台上完成工作)。
typedef void (*FPtr)(void); // Hide the ugliness
FPtr f = someFunc; // Function pointer to convert
void* ptr = *(void**)(&f); // Data pointer
FPtr f2 = *(FPtr*)(&ptr); // Function pointer restored
当涉及到数据指针和代码指针时,我有三条经验法则:
在以下功能中:
void setCallback(const void *fnPointer)
{
gfnPtr = *((FN_GET_VAL*) (&fnPointer));
}
你有一个数据指针,你需要一个函数指针。 (更不用说你通过首先获取指针本身的地址,将其转换为指向指针的指针,然后取消引用它)来实现这一点。
尝试将其重写为:
void setCallback(FN_GET_VAL fnPointer)
{
gfnPtr = fnPointer;
}
此外,您可以(或应该)在设置指针时删除强制转换:
main()
{
setCallback(myfunc);
gfnPtr();
}
作为额外的奖励,您现在可以使用编译器执行的常规类型检查。
我会建议一个可能的部分解释。
@Manoj如果你检查(或可以提供)两个编译器生成的SetCallback的汇编列表,我们可以得到明确的答案。
首先,Pascal Couq的陈述是正确的,Lindydancer展示了如何正确设置回调。我的回答只是试图解释实际问题。
我认为问题源于Linux和其他平台使用不同的64位模型(参见64-bit models on Wikipedia)。请注意,Linux使用LP64(int为32位)。我们需要更多关于其他平台的细节。如果是SPARC64,则使用ILP64(int为64位)。
据我了解,这个问题只能在Linux下观察到,如果你引入了一个int局部变量就会消失。您是否尝试过优化或关闭优化?最有可能的是,这种黑客攻击对优化没有任何好处。
在这两种64位模型下,指针应该是64位,无论它们是指向代码还是数据。但是,情况可能并非如此(例如分段存储器模型);因此,Pascal和Lindydancer的劝告。
如果指针大小相同,剩下的可能是堆栈对齐问题。引入本地int(在Linux下为32位)可能会改变对齐方式。如果void *和函数指针具有不同的对齐要求,这只会产生影响。一个令人怀疑的情况。
然而,不同的64位内存模型很可能是您观察到的原因。欢迎您提供装配清单,以便我们对其进行分析。
与其他人说的不同,是的,你可以将void*
指针作为函数指针,但语义使用起来非常棘手。
正如你所看到的,你不需要把它作为void*
投射,只需像平时那样分配它。我像这样运行你的代码,我编辑它工作。
file1.c中:
typedef unsigned long (*FN_GET_VAL)(void);
extern FN_GET_VAL gfnPtr;
void setCallback(const void *fnPointer)
{
gfnPtr = ((FN_GET_VAL) fnPointer);
}
file2.c中:
int main(void)
{
setCallback(myfunc);
(( unsigned long(*)(void) )gfnPtr)();
}