2011-04-07 171 views
15

我试图运行下面的程序,但得到一些奇怪的错误:C函数指针铸造空指针

文件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 */ 
} 

这里gfnPtr()在使用gcc编译时在64位suse linux上崩溃。但它成功地调用了gfnPtr()VC6和SunOS。

但是,如果我改变了下面给出的功能,它工作正常。

void setCallback(const void *fnPointer) 
{ 
    int i; // put any statement here 
    gfnPtr = *((FN_GET_VAL*) (&fnPointer)); 
} 

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

回答

26

该标准不允许将函数指针转换为void*。您只能投射到另一个函数指针类型。 6.3.2.3§8:

一个指针,指向一种类型 的功能可以被转换为一个指针到另一个类型的 功能和背面 再次

重要的是,必须投退在使用指针调用函数之前(在技术上,指向兼容类型,在6.2.7中定义“compatible”)。

+0

感谢您的答复。我会小心不要混合数据和函数指针。但在这种情况下,我无法弄清楚发生这种情况的原因。如果我用-m32(32位)编译并运行,它工作得很好,但是如果我使用-m64(64位)编译它会给出问题。另外,如果我添加一个像** int i; **这样的语句,然后它就可以正常工作了。不知道可能堆栈损坏的原因,但如何检查。 – Manoj 2011-04-07 11:28:49

9

我有经验的三个规则的时候才来的数据指针和代码指针:

  • 不要混合数据指针和代码指针
  • 不要混合数据指针和代码指针
  • 不要有史以来混合数据指针和代码指针!

以下功能:

void setCallback(const void *fnPointer) 
{ 
    gfnPtr = *((FN_GET_VAL*) (&fnPointer)); 
} 

你有数据指针,你的情况下,以一个函数指针。 (更不用说,你通过首先获取指针本身的地址来做到这一点,在解除引用之前将它指向指向指针的指针)。

尝试把它改写为:

void setCallback(FN_GET_VAL fnPointer) 
{ 
    gfnPtr = fnPointer; 
} 

此外,您还可以(或应该)下降设置指针当铸钢:

main() 
{ 
    setCallback(myfunc); 
    gfnPtr(); 
} 

额外的好处是,你现在可以使用由编译器执行的正常类型检查。

+0

感谢您的回复。我会小心不要混合数据和函数指针。但在这种情况下,我无法弄清楚发生这种情况的原因。如果我用-m32(32位)编译并运行,它工作得很好,但是如果我使用-m64(64位)编译它会给出问题。另外,如果我添加一个像** int i; **这样的语句,然后它就可以正常工作了。不知道可能堆栈损坏的原因,但如何检查。 – Manoj 2011-04-07 11:25:37

+0

对于分析来说,获取函数指针的地址与将&赋值给函数名称(这是一个无操作)不同。 – 2013-05-22 04:43:11

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位。然而,这可能不是这种情况(例如分段存储模型);因此,帕斯卡尔和林德塞瑟的告诫。

如果指针大小相同,剩下的就是可能的堆栈对齐问题。引入一个本地int(在Linux下是32位)可以改变对齐。如果void *和函数指针具有不同的对齐要求,这只会起作用。可疑的情况。

尽管如此,不同的64位内存模型很可能是您观察到的原因。欢迎您提供汇编列表,以便我们分析它们。

+0

您链接到的维基百科条目是错误的。 Sparc上用于Solaris的64位ABI是LP64而不是ILP64。顺便说一句SPARC64是富士通独家Sparc实施的品牌名称。 – 2016-02-23 12:44:06

12

该标准不幸地不允许在数据指针和函数指针之间进行转换(因为这在一些非常模糊的平台上可能没有意义),尽管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 
+0

“,因为这在一些非常晦涩的平台上可能没有意义”真正晦涩的平台,如Linux,Mac OS X,iOS,Microsoft Windows和Android,它们都具有数据执行保护功能。通过数据执行保护,代码和数据不可互换。注意:POSIX要求强制转换为void指针,但它不要求这样的结果指针可用,除非转换回函数指针。 – 2013-09-08 02:45:26

+0

@JonathanBaldwin你说的话只有在代码指针的大小不同于数据指针的平台上才有意义。指针只是一个地址,因此永远不会在可执行区中。但是,地址指向的数据可能位于可执行区域。所以你可能无法读取指针指向的内存。但是你当然可以使用这个方法将它从函数指针转换为void *并从void *转换为函数指针。除非平台对于函数指针的大小不同于数据指针,否则它将起作用。 – rxantos 2014-02-20 08:09:41

+0

@rxantos当然,从数据到函数指针的强制转换在使用DXP的ia32系统上执行起来并不重要,但它没有任何意义,因为这样的指针无法转换为数据指针或使用系统调用而变得不可用在DXP中打孔(即使区域可执行)。就C而言,支持这一点不值得为您提到的平台提供不一致的支持,其中包括16位DOS和Windows(请记住近点和远点指针? )毕竟,如果一个平台,比如POSIX,想直接允许这个,他们可以把它作为一个扩展。 – 2014-02-21 08:51:43

0

不像别人说什么,是的,你可以有一个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)(); 
}