我正在尝试运行以下程序但遇到一些奇怪的错误:
档案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));
}
请帮助解决问题的原因。感谢。
答案 0 :(得分:34)
C标准不允许将函数指针强制转换为void*
。您可能只会转换为另一个函数指针类型。在C11 standard, 6.3.2.3 §8:
指向一种类型函数的指针 可以转换为指向a的指针 另一种类型和背部的功能 再次
重要的是,在使用指针调用函数之前必须强制转换回原始类型(从技术上讲,要转换为兼容类型。6.2.7处的“compatible”定义。
请注意,由于使用它们的上下文,许多(但不是全部)C编译器必须遵循的POSIX标准要求将函数指针转换为void*
并返回。这对于某些系统功能是必要的(例如dlsym
)。
答案 1 :(得分:14)
遗憾的是,标准不允许在数据指针和函数指针之间进行转换(因为这可能在某些非常模糊的平台上没有意义),即使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
答案 2 :(得分:9)
当涉及到数据指针和代码指针时,我有三条经验法则:
在以下功能中:
void setCallback(const void *fnPointer)
{
gfnPtr = *((FN_GET_VAL*) (&fnPointer));
}
你有一个数据指针,你需要一个函数指针。 (更不用说你通过首先获取指针本身的地址,将其转换为指向指针的指针,然后取消引用它)来执行此操作。
尝试将其重写为:
void setCallback(FN_GET_VAL fnPointer)
{
gfnPtr = fnPointer;
}
此外,您可以(或应该)在设置指针时删除演员:
main()
{
setCallback(myfunc);
gfnPtr();
}
作为额外奖励,您现在可以使用编译器执行的普通类型检查。
答案 3 :(得分:2)
我会建议一个可能的部分解释。
@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位内存模型很可能是您观察到的原因。欢迎您提供装配清单,以便我们对其进行分析。
答案 4 :(得分:-1)
与其他人说的不同,是的,你可以将一个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)();
}