C函数指针转换为void指针

时间:2011-04-07 10:46:06

标签: 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)
{
    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));
}

请帮助解决问题的原因。感谢。

5 个答案:

答案 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)();
}