C陷阱与缺陷 第2章 语法“陷阱” 2.1 理解函数声明

    理解函数声明
    当计算机启动时,硬件将调用首地址为0位置的子例程。
    (*(void(*)())0)();
    按照使用的方式来声明。 

    声明构成: 
    任何C变量的声明都由两部分组成:类型以及一组类似表达式的声明符(declarator)。对它求值返回一个声明中给定类型的结果。

    最简单的声明符就是单个变量,如 
    float f, g;  
    声明的含义是:当对其求值时,表达式f和g的类型为浮点数类型(float)。因为声明符和表达式相似,所以我们也可以在声明符中任意使用括号。 
    float ((f));  
    声明的含义是:当对其求值时,((f))的类型为浮点类型,由此可以推知,f也是浮点类型。

    同样的逻辑也适用于函数和指针的声明,例如 
    float ff();   
    声明的含义是:表达式ff()的求值结果是一个浮点数,也就是说,ff是一个返回值为浮点类型的函数。
    float *pf;   
    声明的含义是:*pf是一个浮点数,也就是说,pf是一个指向浮点数的指针。

    以上这些形式在声明中还可以组合起来,就像在表达式中进行组合一样。因此,
    原因:()集合优先级高于*,*g()也就是*(g()):
    float *g(), (*h)(); /* g是一个函数,该函数的返回值类型为指向浮点数的指针。*/
                              /* h是一个函数指针,h所指向函数的返回值为浮点类型。*/
                              /* *g(), (*h)() 是浮点表达式。*/

    类型转换符:只需要把声明中的变量名和声明末尾的分号去掉,再将剩余的部分用一个括号整个“封装”起来即可。
    float (*h)();
    声明的含义:h是一个指向返回值为浮点类型的函数的指针。
    因此, 
    (float (*)());  
    表示一个“指向返回值为浮点类型的函数的指针”的类型转换符。

    我们现在可以分两步来分析表达式 
    (*(void(*)())0)();  
    变量fp是一个函数指针,调用fp所指向的函数,调用方法如下: 
    (*fp)(); 
    声明的含义:fp是一个函数指针,那么*fp就是就是该指针所指向的函数。
    调用方式:(*fp)(),ANSI C标准允许程序员将上式简写为fp(), 这种写法只是一种简写形式。 
    原因:函数运算符()的优先级高于单目运算符*。 
    *fp()实际上与*(fp())的含义完全一致。ANSI C把它作为*((*fp)())的简写形式。 
    *fp(); 
    与 
    *(fp());
    和 
    *((*fp)());
    是等效的。 

    我们将在分析的第二步来解决这个问题。如果C编译器能够理解我们大脑中对于类型的认识,那么我们可以这样写: 
    (*0)(); 
    上式并不能生效,因为运算符*必须用一个指针来作为操作数。不仅如此,这个指针还应该是函数指针,这样经运算符*作用后的结果才能作为函数被调用。因此,在上式中必须对0作类型转换,转换后的类型可以大致描述为:“指向返回值为void类型的函数的指针”。

    fp是一个指向返回值为void类型的函数的指针,那么(*fp)()的值是void,fp的声明如下: 
    void (*fp)();
    因此,我们可以用下式来调用存储位置为0的子例程: 
    void (*fp)(); 
    (*fp)();
    此处作者假设fp默认初始化为0,这种写法不宜提倡。
    这种写法的代价是多声明了一个“哑”变量。
    知道如何声明一个变量,自然也就知道如何对一个常数进行类型转换,将其转型为该变量的类型:
    只需要在变量声明中将变量名去掉即可。
    将常数0转型为“指向返回值为void的函数的指针”类型,可以这样写:
    (void (*)()) 0 //pointer to point to function and return type of the function is type void
    因此,我们可以用((void(*)())0)来替换fp,从而得到: 
    (*(void (*)()) 0)();
    末尾的分号使得表达式成为一个语句。

    使用typedef能够使表述更加清晰: 
    typedef void (*funcptr)();
    (*(funcptr)0)();
    与 
    (*(void (*)())0)();
    意义相同。 

    首先,让我们从用户定义的信号处理函数开始考虑,这无疑是最容易解决的。 
    void sigfunc(int n) {
        /*特定信号处理部分*/ 
    }
    sigfunc的参数是一个代表特定信号的整数值
    上面假设的函数体定义了sigfunc函数,因而sigfunc函数的声明可以如下: 
    void (*sfp)(int);
    现在假定我们希望声明一个指向sigfunc函数的指针变量,不妨命名为sfp。因为sfp指向sigfunc函数,则*sfp就代表了sigfunc函数,所以*sfp可以被调用。又假定sig是一个整数,则(*sfp)(sig)的值为void类型,因此我们可以如下声明sfp: 
    void (*sfp)(int);
    上式显示了如何声明signal函数。因为signal函数的返回值类型与sfp的返回类型一样,我们可以如下声明signal函数:
    void (*signal(something))(int);
    此处的something代表了signal函数的参数类型。上式声明可以这样理解:传递适当的参数以调用signal函数,对signal函数返回值(为函数指针类型)解除引用(dereference),然后传递一个整型参数调用解除引用后所得函数,最后返回值为void类型。因此,signal函数的返回值是void类型的函数的指针。 
    signal库函数,在包括该函数的C编译器实现中,signal函数接受两个参数;一个是代表需要“被捕获”的特定signal的整数值;另一个是指向用户提供的函数的指针。该函数用于处理“捕获到”的特定signal,返回值类型为void。我们此前已经定义了指向用户定义的信号处理函数的指针sfp:
    void (*sfp)(int);
    sfp的类型可以通过将上面声明中的sfp去掉而得到,即void (*)(int)。此外,signal函数的返回值是一个指向调用前的用户定义信号处理函数的指针,这个指针的类型与sfp指针类型一样。因此,我们可以如下声明signal函数: 
    void (*signal(int, void(*)(int)))(int);
    同样地,使用typedef可以简化上面的函数声明: 
    typedef void(*HANDLER)(int);
    HANDLER signal(int, HANDLER);

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

weixin_40186813

你的能量无可限量。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值