这是一句很长很长而且又很啰嗦并且很无聊的废话...
分类: C/C++
2011-05-13 22:21:30
看了C陷阱与缺陷,第一个给我震撼的就是理解函数声明了,下面是我的理解。
1.理解函数声明
为了模拟开机启动时的情形,我们必须设计出一个C语句,以显示调用位于地址0的子例程。调用语句如下:
(*(void (*)())0)();
胆颤了吧?首先我们从函数的声明说起:有如下一个函数
void func(){...}
那么,要想声明一个函数指针,指向这类函数,怎样声明呢?如下:
void (*pf)();
那么,想要将一个值转换成一个指针,指向这类函数,怎么强制转换呢?如下:
(void (*)())value
现在value被转换成了一个指向函数的指针,怎么调用它呢?如下:
((void) (*)()value)();
或 (*(void) (*)()value)();
或 (******(void) (*)value)();
神奇吧,看了上面的函数调用方式!其实你完全可以这样调用函数:
func();
(*func)();
(********func)();
没有任何问题!
对于初学的时候,最容易头晕的就是指针符*到底是什么东西?它是变量的一部分?还是声明类型的一部分?其实,仔细回忆C中标识符的定义规则就知道,指针符*必须是类型声明的一部分,因为变量的声明不能含有指针符号,否则是一个非法的变量!
2.C语言中的声明
对比简单类型的强制转换我们就可以更加明白上面的强制转换,简单类型定义如下:
int a;
char b;
b=(char)a;
int *a;
char *b;
b=(int *)a;
其实,在各种指针之间转换很少见,最常见的就是将void指针转换成各种指针,用过malloc族函数吗?
a=(int *)malloc(sizeof(int));
由于声明符和表达式类似,所以你可以这样声明
int ((a)); //将a声明为一个int型值
int *func(),(*fp)();
前者是一个函数,后者是一个指针,所以千万不要对指针定义成函数了,分不清概念的时候最容易这样
int (*fp)(){
/*do something*/
}
显然,这里将一个指针定义成了一个函数!这是任何编译器都不能容忍的。
结合简单变量的类型,出去声明中的变量,得到的就是这个变量的类型,如float a,那么要将一个变量转换成a的类型,那么只需要
(float)value;即可,同理:
float *b,除去变量b,得到b的类型是(float *)。
float (*fp)();除去变量fp,得到的类型是float (*)(),所以要将变量转换成fp类型的值时,只需要(float (*)())value,即可!这样value即被转换成一个函数指针了,也就像最前面的例子中那样!
如果,更复杂,有声明如下:
float * (*fp)(); //返回一个float指针
其实,这也是唬人的,同理可知:强制转换类型为(float * (*)())value;
3.其他考量
看到第一个例子时,可能想,能不能这样子:(*0)();呢?
不行,因为0是一个数字,而*必须要操作一个指针,而且对于要调用的函数是void型的,所以这个指针应该转换成相应的类型,所以需要将0转成一个指向void返回值的参数为空的函数的指针。
最后,linux内核中的信号处理函数定义如下:
void (*signal(int,void (*)(int)))(int);
首先,将上面的函数声明看成这样void (*p)(int);可知,p是一个函数,所以signal函数的返回类型为一个函数指针,指向的函数类型是
void (*)(int);使用typedef可以简化上面signal函数的声明
typedef void (*HANDLER)(int);
HANDLER signal(int,HANDLER);
解释一下:signal是一个返回函数指针的函数,返回的指针指向的函数类型是void (*)(int);而signal的参数一个是int,另一个是一个函数,类型为void (*)(int),刚好就是最初声明的样子。
摘自C陷阱和缺陷一书,但被网上一个自认为是高手的挫人改成这个样子,凑合着看吧
声明与函数 有一段程序存储在起始地址为0的一段内存上,如果我们想要调用这段程序,请问该如何去做?
答案 答案是(*(void (*)( ) )0)( )。看起来确实令人头大,那好,让我们知难而上,从两个不同的途径来详细分析这个问题。
答案分析:从尾到头
首先,最基本的函数声明:void function (paramList); 最基本的函数调用:function(paramList); 鉴于问题中的函数没有参数,函数调用可简化为 function();
其次,根据问题描述,可以知道0是这个函数的入口地址,也就是说,0是一个函数的指针。使用函数指针的函数声明形式是:void (*pFunction)(),相应的调用形式是: (*pFunction)(),则问题中的函数调用可以写作:(*0)( )。
第三,大家知道,函数指针变量不能是一个常数,因此上式中的0必须要被转化为函数指针。我们先来研究一下,对于使用函数指针的函数:比如void (*pFunction)( ),函数指针变量的原型是什么? 这个问题很简单,pFunction函数指针原型是( void (*)( ) ),即去掉变量名,清晰起见,整个加上()号。所以将0强制转换为一个返回值为void,参数为空的函数指针如下:( void (*)( ) )。 OK,结合2)和3)的分析,结果出来了,那就是:(*(void (*)( ) )0)( ) 。
答案分析:从头到尾理解答案
(void (*)( )) ,是一个返回值为void,参数为空的函数指针原型.(void (*)( ))0,把0转变成一个返回值为void,参数为空的函数指针,指针指向的地址为0. *(void (*)( ))0,前面加上*表示整个是一个返回值为void的函数的名字 (*(void (*)( ))0)( ),这当然就是一个函数了。我们可以使用typedef清晰声明如下: typedef void (*pFun)( ); 这样函数变为 (*(pFun)0 )( );
问题:三个声明的分析 对声明进行分析,最根本的方法还是类比替换法,从那些最基本的声明上进行类比,简化,从而进行理解,下面通过分析三个例子,来具体阐述如何使用这种方法。
#1:int* (*a[5])(int, char*);
首先看到标识符名a,"[]"优先级大于"*",a与"[5]"先结合。所以a是一个数组,这个数组有5个元素,每一个元素都是一个指针,指针指向" (int, char*)",很明显,指向的是一个函数,这个函数参数是"int, char*",返回值是"int*"。OK,结束了一个。:)
#2:void (*b[10]) (void (*)());
b是一个数组,这个数组有10个元素,每一个元素都是一个指针,指针指向一个函数,函数参数是"void (*)()"【注10】,返回值是"void"。完毕! 注意:这个参数又是一个指针,指向一个函数,函数参数为空,返回值是"void"。
#3. double(*)() (*pa)[9]; pa是一个指针,指针指向一个数组,这个数组有9个元素,每一个元素都是"double(*)()"(也即一个函数指针,指向一个函数,这个函数的参数为空,返回值是"double")。
(自己注:#3看起来有点难理解,如果把double(*)()换成int, 变成int (*pa)[9]这样就不难理解了)