C语言中的函数指针类型转换


今天在看PHP5.3源码中zend_API.h中ZEND_INIT_MODULE_GLOBALS的定义时,发现了其有2个版本。一个为ZTS,另一个为非ZTS。这两个版本对于全局量的初始化函数——globals_ctor的使用是不一样的。非ZTS的版本中,globals_ctor只有一个参数,即“globals_ctor(&module_name##_globals)”;在在ZTS版本中,我看到了“(ts_allocate_ctor) globals_ctor”,而ts_allocate_ctor的定义为“typedef void (*ts_allocate_ctor)(void *, void ***);”,也就是说该操作将一个只有一个参数的函数指针强制转换为拥有两个参数的函数指针。顿时惊呆了,函数指针竟然可以这么转换。于是,开始到网上搜相关资料,发现没有讲得很全、很通的。所以,只能将它们都列下来,以备以后研究。


1. 函数指针的强制类型转换 (http://welchjames.blog.163.com/blog/static/139884835201082734334601/)

C语言中的强制类型转换是一个很常用的技巧,在编程中经常会用到。形式简单的类型转换一目了然,然而复杂一些的却常常令人困惑。这两天读代码发现一个函数指针的类型转换:
((void (*)(void)) (entry ))();
虽然知道是通过函数指针进行函数调用,但是和见过的函数指针的形式都不一样,想了半天也不明白,最后到网上搜了一下,才知道是一个类型转换。
函数指针的声明形式:
void (*pFunction)(),当然,没有参数的情况下也可写成void (*pFunction)(void)的形式。那么pFunction函数指针的原型就是
void (*)(void),即把变量名去掉,因此,对于一个给定的entry地址,要把它转换成为函数指针,就是
(void (*) (void))entry

对于函数指针的调用,ANSI C认为 pFunction()和*pFunction()都是正确的,所以
((void (*) (void))(entry)();
就形成一个函数调用。


2. 函数指针,也能类型转换,C的语法顽强啊(http://hi.baidu.com/dudangyimian/item/f3840b6edeb95f156895e68d)

C语言的类型转换非常随意,C++就严格多了,近来试了一下C语言中函数指针类型转换,也很随意(http://hi.baidu.com/dudangyimian/item/f3840b6edeb95f156895e68d):
0001// 函数指针类型,也可以转换
0002#include
0003
0004typedefvoid (*twoparam)(int*, int*);
0005
0006void oneparam(int z)
0007{
0008    printf("the parameter is:%3d", z);
0009}
0010
0011int main()
0012{
0013    twoparam tCanUse = (twoparam)oneparam;
0014
0015    tCanUse((int*)100, (int*)1000);
0016
0017    return 0;
0018}
  这太方便了。glib中就是这么用的。我也来推荐大家用。


3. 函数指针的强制类型转换(http://www.cppblog.com/willcao/archive/2008/09/30/functiontest.html):

指针应该都是4个字节,指向32位的地址.可以寻访4GB的内存.如果是64位就再说.所以对函数指针来说这个应该就有了很大的好处.因为指针大家都是4个字节不论是什么种类的函数,它肯定都是4字节.这样赋值就没问题.在这里你也可以将指针直接看成是一个整数.这样会更明白些.而对于另外一个问题.函数参数和返回值,则完全由函数的定义来决定.嗯.这样就可以有很大的自由空间.来段代码.

 1 #include < iostream >
 2 using   namespace  std ;
 3
 4 typedef  void  ( * pfn) ( void );
 5 union msg
 6 {
 7    pfn first ;
 8    int (* ifn)(int a ,int b );
 9    void(*vfn)(int ,int );
10}
;
11 int  OnInt( int  a , int  b )
12 {
13    cout<<a<<"    "<<b<<endl;
14    return a ;
15}

16 void  OnVoid( int  a , int  b )
17 {
18    cout<<<<"    "<<b<<endl;
19}

20 int  main()
21 {
22    pfn p=(pfn)(int (*)(int ,int ))OnInt;
23    msg m;
24    m.first=p;
25    cout<<(m.ifn)(5,6)<<endl;
26
27    p=(pfn)(void (*)(intint ))OnVoid;
28    m.first=p;
29    m.vfn(10,15);
30    return 0;
31}
看了这段代码会让人想到什么呢?想到的应该是MFC中那些消息函数吧.不同的消息,参数不一样,返回值也不一样.而在定义的时候只是一个指针,可是在调用的时候却有各种各样的方式.另外这段代码最有意思的就是打破常规,就用了union同时只有一个变量在起作用,平时书上总是说其他变量都不能用,今天就用给你看看,用的还很牛...


4. 函数指针的强制类型转换(http://blog.csdn.net/syrchina/article/details/6780107):

  1. /******************************************************************************* 
  2. ** 程序名称:函数指针的强制类型转换  
  3. ** 程序描述: 
  4. ** 性能提升: 
  5. ** 程序版本:V1.0 
  6. ** 程序作者:宋元瑞  
  7. ** 最后修改:2011年9月15日  
  8. *******************************************************************************/  
  9. #include   
  10. #define abc  
  11. void syr(void)  
  12. {  
  13.     printf("hello world!\n");  
  14. }  
  15. int main(int argc, char *argv[])  
  16. {  
  17.     void (*pFun)(void);  
  18.     printf("%d\n",syr);  
  19.     pFun = (void(*)(void))(4199152);//这里须保证地址与上面打印出的地址一致   
  20.     pFun();  
  21.       
  22.     return 0;  
  23. }  

5. C 语言中统一的函数指针(http://blog.codingnow.com/2010/07/function_c.html):

有时候,我们需要把多个模块粘合在一起。而这些模块的接口参数上有少许的不同。在 C 语言中,参数(或是返回值)不同的函数指针属于不同的类型,如果混用,编译器会警告你类型错误。

在 C 语言中,函数定义是可以不写参数的。比如:

void foo();

这个函数定义表示了一个返回 void 的函数,参数未定。也就是说,它是个弱类型,诸如:

void foo(int);

void foo(void *);

这些类型都可以无害的转换成它。正如在 C 语言中,具体的指针类型如 int * ,char * 都可以转换为 void * 一样。

注1:如果要严格定义一个无参数的函数,应该写成 void foo(void);

注2:如果有部分参数固定,而其后的参数可变,则定义看起来是这样: void foo(int , ...); 这表示第一个参数为 int ,从第 2 个参数开始可变。

不过,C 语言的这种语法,实际上不太使用。因为用 C 语言无法主动控制函数调用的参数压栈。我们很难根据程序的上下文来决定如何传入参数去调用某个函数。如果需要逐级传递多个函数的参数,用的更多的是 va_list

比如,你很难对 printf 做封装,通常为了方便做封装,还提供了形为 vprintf 的接口。

C++ 解决此类问题的方案是用类去模拟一个函数,通过重载 () 操作符的方法,让函数调用看起来和普通函数一致(并美其名曰 functor/仿函数)。当然,也有撕破语法糖的伪装,用更直白的类继承的方式来定义出接口。

这里想说的是,C 语言里也还有一种有趣的方案来在保证类型安全的基础上解决类似问题。

在 X-Window 的消息定义中就可以看到这样的手法。

在 Windows 的接口中,Windows 的消息携带的数据通常用两个参数来表示:WPARAM 和 LPARAM ,均为 32bit 整数。我们知道,消息本质上等同于对象的方法。在更早的面向对象语言如 smalltalk 中,调用对象的方法即被看成向对象发送一个消息。Windows 如此把所有消息处理相关函数的接口都以 WPARAM 与 LPARAM 的形式传递参数,正是为了方便统一其接口形式。各种五花八门的参数都蕴涵于这 64 bit 数据中。

Xlib 处理类似的问题,对 C 程序员的亲合力则大的多。至少更为类型安全。

Xlib 定义了一个叫做 XEvent 的结构体(实际是一个 union)。然后把各种可能的消息类型放在这个 union 中。例如,我想取键盘消息,则可以用 event.xkey.keycode 。

一般说来,我们可以把模块的对外接口看成是接收一组输入参数并加以处理。如果需要粘合多个不同的模块,他们需要处理不同的输入参数的话,可以借鉴 XLib 的这个方法。在粘合层定义一个 union ,把所有可能的参数组,每组定义成一个 struct 然后定义在同一个 union 中。这个粘合层的统一接口则为这个 union 指针。有必要的话,所有的参数组 struct 的头部都留下 type 字段。这样比较容易分发消息。

这样做的本质是:把函数调用时由编译生成的、将调用参数逐个压栈的代码,改由程序员主动填写(填写参数结构体)。利用结构的类型安全,保证了函数调用时的参数类型安全。再利用 union 的语法,把不同的参数组联合到一起变成同一类型。

给 api 传递一个 struct 或 union 指针而不是逐个参数传递,是 C 接口设计的一种常见手法。除了 XLib 的设计,还能找到很多耳熟能详的例子。例如,我们在 socket api 上也可以看到类似的东西。例如 connect 的参数中有一 sockaddr 结构,就适用于各种不同的网络底层协议。

该篇文章一个比较好的评论:

C语言本来就有"统一的函数指针", 任何一种函数指针类型都是。

任何一种函数指针都可以转型为其他类型的函数指针, 当转型回来时, 与原函数指针相同。

类型安全也是完全一样。
即使是union, 存入什么, 也必须取出什么 —— 必须由程序员自己(或者用另外某个变量)记住究竟是什么类型。
取错了, 或者转型错了, union或者cast都完蛋。

union的扩展性反而不如函数指针好。 定义一个union也麻烦。
只是使用时少写一个转型比较方便而已。


你可能感兴趣的:(C/C++)