c陷阱与缺陷学习笔记-第二章-语法陷阱

理解函数声明

任何c变量的声明都由两部分组成:类型以及一组类似表达式的声明符。声明符从表面上看与表达式有些类似。对它求值应该返回一个声明中给定类型的结果。最简单的声明符就是单个符号

float f,g;

这个声明的含义就是:当对其求值时,表达式f和g的类型为浮点型类型。因为声明符与表达式的相似,所以我们也可以在声明符中任意使用括号。

float ((f));

这个声明的含义是:当对其求值的时候,((f))的类型是浮点类型,由此可知,f也是浮点类型。

同样的逻辑也适用于函数和指针类型的声明,例如:

float ff();

这个声明的含义是:表达式ff()求值结果是一个浮点数,也就是说,ff是一个返回值为浮点类型的函数,类似的

float *pf;

这个声明的含义是 pf是一个浮点数,也就是说pf是一个指向浮点数的指针
以上这些形式在声明中还可以组合起来,就像在表达式中进行组合一样,因此

float * g(),(*h)();

表示*g()与(h)()是浮点表达式,因为()结合优先级高于,*g()也就是*(g()),g是一个函数,该函数的返回值类型为指向浮点数的指针。同理,可以得出h是一个函数指针,h所指向函数的返回值为浮点类型。
一旦我们知道了如何声明一个给定类型的变量,那么该类型的类型转换符就很容易得到了:只需要把声明中的变量名和声明末尾的分号去掉,再将剩余的部分用一个括号整个封装i起来。例如,

float (*h)();

表示h是一个指向返回值为浮点类型的函数的指针

(float(*)())

表示一个指向返回值为浮点类型的函数的指针的类型转换符

分析((void()())0)()

现在我们来分析(*(void(*)())0)()
第一布,极爱的那个fp是一个函数指针,那么如何调用fp所指向的函数呢?调用方法如下:

(*fp)();

因为fp是一个函数指针,那么*fp就是该指针所指向的函数,所以(*fp)()就是掉头用该函数的方法。标准准许程序员把上面的式子简写为fp()。但是一定要记住这只是一种简写形式在表达式(*fp)(),两侧的括号非常重要,因为()的优先级高于星号,如果没有括号,那么*fp()*(fp())的含义完全一样
现在就是要找到一个恰当的表达式来替换fp,我们在第二部来解决这个问题。如果c编译器能够理解我们大脑中对于类型的认识,那么我们应该这样写

(*0)();

但是上面的式子并不能生效,因为型号必须要一个指针来做操作数。而且这个指针应该是一个函数指针,这样星号作用后的结果才能作为函数被调用。所以必须对0进行类型转换,转换后的类型可以描述为指向返回值为void类型的函数的指针
如果fp是一个指向返回值为void类型的函数的指针,那么(*fp)()的值为void,fp的声明为;

void (*fp)();

因此,我们可以用下式来完成调用存储位置为0的子例程

void (*fp)();
(*fp)();

这种写法的代价是多声明了一个哑变量
但是我们一旦知道了怎么声明一个变量,也就自然知道如何对一个常数进行类型转换,将其转型为该变量的类型;只需要在变量声明中将变量名去掉就可以了。
因此,将常数0转型为指向返回值为void的函数的指针类型,可以这样写

(void (*)()) 0

因此,我们可以用(void(*)())0来替换fp,可以得到

(*(void(*)())0)();

也可以使用typedef来使得表诉更加清晰

typedef void (*funcptr)();
(*(funcptr)0)();

还有例如我们在考虑signal库函数 ,在包括该函数的从、编译器实现中,signal函数接收两个参数,一个是代表需要被捕获的特定signal的整数值,返回值类型为void。
一般情况下,程序员不主动声明signal函数,而是直接使用系统头文件的signal.h中的声明,那么在signal.h中,signal函数是如何声明的
首先,我们从用户定义的信号处理函数开始考虑

void signfunc(int n){
/*特定信号处理部分*/

函数sigfunc的参数是一个代表特定信号的整数值
上面假设的函数体定义了sigfunc函数,所以sigfunc函数的声明可以如下

void sigfunc(int);

现在假定哦我们希望声明一个指向sigfunc函数的指针变量,我们不妨命名为sfp。
因为sfp指向sigfunc函数,那么*sfp就代表了sigfunc函数。我们假定sig是一个证书,那么(*sfp)(sig)的值为void类型,所以sfp的声明为

void(*sfp)(int);

因为signal函数的返回值类型和sfp的返回类型一样,上面的式子也就声明了signal函数,我们可以如下声明signal函数

void (*signal(something))(int)

这里首先,signal(something)是个函数,先返回一个值(这应该知道吧),再*(解引用)对吧。那么返回值的类型是什么呢?signal这个函数返回的是一个函数指针,书里一笔带过了。然后再解引用,由函数指针变成一个普通函数。关键就是理解signal(something)返回值是一个函数指针,类型和p一样。
此处的something代表了signal函数的参数类型。上面声明可以这样理解:传递适当的参数以调用signal函数,对signal函数返回值解除引用,如何传递一个整型参数调用接触引用后所得函数,最后返回值为void类型。因此,signal函数的返回值是一个指向返回值为void类型的函数的指针

那么,signal函数的参数又是如何呢?signal函数接收两个参数:一个整型的信号编号,以及一个指向用户定义的信号处理函数的指针。我们之前已经定义了指向用户定义的信号处理函数的指针sfpvoid (*sfp)(int)
sfp的类型可以通过将上面的声明中的sfp去掉而得到,即void(*)(int)。
signal函数的返回值是一个指向调用前的用户定义信号处理函数的指针,这个指针的类型与sfp指针类型一致。
所以,我们可以如下声明signal函数

void(*signal(int,void(*)(int)))(int)

同样的,使用typedef可以简化上面的函数

typedef void (*HANDLER)(int);
HANDLER signal(int,HANDLER);
void (*f(int, void (*)(int)))(int)

 

以前在C trap pit fails里面见过,但是文章里面介绍的很详细,但是往往使初学者抓不到重点, 结果弄的一头污水。这里就简单介绍一下这中函数指针的定义方法。

 

什么是函数指针?
 

这个问题从定义的角度来看很好理解,指向函数的指针就是函数指针,但是我们如何声明一个函数指针呢?又如何将一个地址强制转换为某一个类型的函数指针呢?这里看下面一个例子源码:

void function(int a)

{

       a = 5;

}

void (*pfunc)(int);

 

很简单,上面这段代码声明了一个函数fucntion和一个函数指针pfunc, 它指向的函数就是一个具有void返回值,int参数的函数。如果将function函数的地址给pfunc指针,可以简单的通过下面两种赋值:

       pfunc = function;

或者

       pfunc = &function;

通过指针调用该函数,也有两种方法:

       pfunc(5); 或 (*pfunc)(5);

我们看一下赋值语句,pfunc = function; 但有时候可能是一个常数0x8999940, 它恰好也表示一个安全的与function相同的函数,如何将这个数值赋给pfunc呢?显然我们需要强制类型转换,应该将该常数转换成什么类型呢?这就是问题的关键!

       在void (*pfunc)(int)语句里面,只有pfunc是变量名称,那么剩余的部分,void(*)(int),就是我们需要的转换类型。因此,新的赋值语句是:

                     pfunc = (void (*)(int)) 0x8999940;

       赋值完成后,就可以通过pfunc(5); 或 (*pfunc)(5);调用相应的函数了。

 

       如果理解了上面的内容,我们就可以解释void (*signal(int, void (*)(int)))(int)这个相对复杂的问题了

 

返回函数指针的函数声名
 

现在我们先抛开上面那个复杂的定义,先看一下下面的需求1) 定义一个函数;2) 该函数具有以下特点,两个参数,返回值是函数指针,并且一个参数也是函数指针。假如返回值和参数函数指针同为void (*)(int); 另一个函数参数是int型。该函数定义名称为my_func。

 

根据需求我们可以很容易定义出这种函数:

 

typedef void (*HANDLER)(int); // 参数函数和返回函数定义

HANDLER my_func(int, HANDLER);

 

突然需求中又不让使用typedef,这就是早期C语言不支持typedef的情况,那么如何定义这种函数呢?

我们假如说my_func的返回值是int,是不是它的定义可以这么写:

int my_func(int, void (*)(int));

       也就是说,my_func(int, void (*)(int))就是一个int型数据。现在将int换成一个函数,也就是

              void (*)(int) my_func)(int, void (*)(int);

       这样一种定义,显然这种语法不支持,那么,实际是如何表示呢?回过头来,我们先看看函数指针的声明格式

       void (*pfunc)(int);

       其中pfunc 等价于 void (*)(int)。现在在看看上面的格式,是不是很相识,对了,pfunc就是my_func(int, void (*)(int))。现在如果将两者代替一下是不是就成了这种格式:

              void (*my_func(int, void(*)(int)))(int)

如果将my_func换成signal,是不是就是我们文章开始提到的那个复杂声名?现在是不是明白了,原来如此啊,它是一个返回函数指针的的函数声名!

猜你喜欢

转载自blog.csdn.net/weixin_42082138/article/details/86630960