C函数指针转换为空指针

C函数指针转换为空指针,c,pointers,function-pointers,C,Pointers,Function Pointers,我试图运行以下程序,但出现一些奇怪的错误: 文件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) {

我试图运行以下程序,但出现一些奇怪的错误:

文件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*
。只能强制转换到另一个函数指针类型。在:

指向一种类型函数的指针 可以转换为指向 另一种类型的功能和背面 再次

重要的是,在使用指针调用函数之前,必须回溯到原始类型(从技术上讲,是指兼容的类型


请注意,POSIX标准要求函数指针可以转换为
void*
并返回,因为许多(但不是所有)C编译器也必须遵循POSIX标准。这对于某些系统功能(例如,
dlsym
)是必需的。

对于数据指针和代码指针,我有三条经验法则:

  • 不要混合使用数据指针和代码指针
  • 不要混合使用数据指针和代码指针
  • 永远不要混合使用数据指针和代码指针
在以下功能中:

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位模型(请参阅)。请注意,Linux使用LP64(int是32位)。我们需要关于另一个平台的更多细节。如果是SPARC64,则使用ILP64(int为64位)

据我所知,这个问题只有在Linux下才能观察到,如果引入一个int局部变量,问题就会消失。您是否尝试过在关闭或打开优化的情况下执行此操作?最有可能的是,这种黑客行为不会对网络的优化产生任何有益的影响

在这两种64位模型下,指针应该是64位的,不管它们是指向代码还是指向数据。然而,情况可能并非如此(例如,分段内存模型);因此,帕斯卡和林迪丹瑟的训诫

如果指针大小相同,剩下的可能是堆栈对齐问题。引入本地int(Linux下为32位)可能会改变对齐方式。只有当void*和函数指针具有不同的对齐要求时,这才会产生效果。令人怀疑的情景


然而,不同的64位内存模型很可能是您观察到的问题的原因。欢迎您提供程序集清单,以便我们对其进行分析。

不幸的是,该标准不允许在数据指针和函数指针之间进行强制转换(因为这在一些真正模糊的平台上可能没有意义),即使POSIX和其他平台需要此类强制转换。一种解决方法不是强制转换指针,而是强制转换指向指针的指针(编译器可以这样做,它将在所有正常平台上完成该工作)


与其他人所说的不同,是的,您可以将
void*
指针作为函数指针,但其语义非常复杂

正如您所看到的,您不需要将其强制转换为
void*
,只需像普通一样分配它即可。我像这样运行了你的代码,并对其进行了编辑

文件1.c:

typedef unsigned long (*FN_GET_VAL)(void);

extern FN_GET_VAL gfnPtr;

void setCallback(const void *fnPointer)
{
    gfnPtr = ((FN_GET_VAL) fnPointer);
}
文件2.c:

int main(void)
{
    setCallback(myfunc);
    (( unsigned long(*)(void) )gfnPtr)();
}

谢谢你的回复。我会注意不要混合使用数据和函数指针。但在这种情况下,我无法找出发生这种情况的原因。如果我用-m32(32位)编译和运行,它工作得非常好,但是如果我用-m64(64位)编译,它就产生了问题。如果我添加一条语句,比如int I;上面的作业,那么它是工作良好。不确定原因可能是堆栈损坏,但如何检查。+1用于分析获取函数指针地址与将
&
应用于函数名(这是一个no op)不同。感谢您的回答。我会注意不要混合使用数据和函数指针。但在这种情况下,我无法找出发生这种情况的原因。如果我用-m32(32位)编译和运行,它工作得非常好,但是如果我用-m64(64位)编译,它就产生了问题。如果我添加一条语句,比如int I;上面的作业,那么它是工作良好。不确定原因可能是堆栈损坏,但如何检查。根据6.3.2.3,我同意标准不允许将函数指针强制转换为
void*
;但我想知道为什么6.3.2.3p3会说“指向任何对象或函数的指针”,因为这是绝对不能发生的。顺便说一句,J.5.7说“指向对象或void的指针可以转换为指向函数的指针,允许数据作为函数调用”,反之亦然,但这是J.5常见的扩展,因此不可移植。@Manoj原因可能是一些优化(即使在编译时没有
-O
)。也许在某些地方,编译器认为函数
myfunc
没有被使用,或者类似的东西。这取决于您使用的标准——POSIX标准要求C编译器支持将函数指针强制转换为void*并安全返回
int main(void)
{
    setCallback(myfunc);
    (( unsigned long(*)(void) )gfnPtr)();
}