• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

浅谈C语言变量声明的解析

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

C语言本身提供了一种不甚明确的变量声明方式——基于使用的声明,如int *a,本质上是声明了*a的类型为int,所以得到了a的类型为指向int的指针。对于简单类型,这样声明并不会对代码产生多大的阅读障碍,而对于复杂的声明,比如标准库的signal函数签名,void (*signal( int sig, void (*handler) (int))) (int),这是什么?一眼看不出来吧,这是一个函数,接受两个参数,一个int,一个函数指针,而这个函数指针指向的函数接受一个int并返回void;返回一个函数指针,这个函数指针指向的函数接受一个int并返回void。尽管很多人都吵着说工程中谁写出来这样的代码就炒他鱿鱼,但人家标准库的确是写出了这样的代码的,你怎么办?

解析这样的复杂声明,我之前见过一种方法——右旋转法,方法是这样的:

  • 从变量名开始,先右再左地,交替地一个一个向外看旁边的token,在纸上写下:“变量是”
  • 若向右遇到左圆括号,在纸上写下:“函数,参数是”,并用同样的方法处理括号中每一个参数——在纸上写下:“返回”
  • 若向右遇到方括号,在纸上写下:“数组,长度为{方括号的内容},元素类型为”
  • 若向右遇到右圆括号,什么也不做
  • 若向左遇到*,在纸上写下:“指针,指向”
  • 若向左遇到任何类型,在纸上写下对应的类型名

我们用这种方法来处理下面的声明

void*(*(*fp1)(int))[10]
  • 从fp1开始——fp1是
  • 向右,遇到右括号,什么也不做
  • 向左,遇到*——指针,指向
  • 向右,遇到左圆括号——函数,参数是int,返回
  • 向左,遇到*——指针,指向
  • 向右,遇到左方括号——数组,长度为10,元素类型为
  • 向左,遇到*——指针,指向
  • 向右,已经到声明结尾,什么也不做
  • 向左,遇到void——void

结果是:fp1是 指针,指向 函数,参数是int,返回指针,指向数组,长度为10,元素类型为 指针,指向 void

这种方法对于人来讲是比较合适的,因为他比较符合人脑的处理方式,但是也有一点缺点,如果函数的形参也写了名字,不是很熟练的小白,就不容易找到正确的起始位置,造成处理的混乱。

对于机器处理,这种从中间到两边的方法就不是很合适了,因为机器并不能直接在一个token序列中直接找到处理的起始位置,他只能从左到右进行扫描,我昨晚灵机一动想到一个算法,今天进行了试验,效果良好,没有对比一些比如cdecl.org那样的开源实现,我这个算法只是一个demo,并不完整支持C声明的处理,地址在这里

算法从左到右扫描,本质上是递归下降,基本过程是这样的,为了方便说明,递归函数名为parse:

  • parse开始
  • if遇到类型,保存进变量a,递归parse,输出a
  • elif遇到*,递归parse,输出"pointer to"
  • elif遇到左圆括号,递归parse,并检查括号匹配
  • elif遇到标识符,输出"{标识符} is"
  • 控制流继续
  • if遇到左方括号,输出"array with length {长度} of",并检查括号匹配
  • elif遇到左圆括号,输出"function accepting",循环地递归parse,吃掉后面的逗号,直到遇到右圆括号,输出"returning"
  • 返回

递归的意义是什么?每一个parse函数的意义都是:“我处理的这段东西的类型是——”,破折号后面的东西右这一层函数退出后上一层函数来补完,所以回去看上面的算法,遇到一个类型的时候,我就明白我下面一层处理的这段东西的类型是int,所以我递归调用parse,并输出int。

再从循环不变式的角度看parse的递归,parse的出口只有一个,那就是遇到的第一个if,不管是什么样的函数声明,最后都是以一个变量结尾的,而这里也是唯一一个把话说全了的分支——其他的分支都是输出类似于“xxx是”,“xxx返回”这样的没说完的话的,所以parse保证上一层的话都没有说完——从不是第一个if的分支退出,由这一层把话补全,这算某种意义上的循环不变式吧。

最后我把上面的声明拆成不同层数来表现一下parse的过程

void
    *              [10]
     (            )
      *      (int)
       (    )
        *
         fp1

鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
C#根据PDF模板生成内容发布时间:2022-07-13
下一篇:
使用C#通过Oracle.DataAccess连接Oracle,部署时需要注意版本问题发布时间:2022-07-13
热门推荐
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap