浅谈C语言变量声明的解析
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
浅谈C语言变量声明的解析的更多相关文章
- 浅谈C语言中的强符号、弱符号、强引用和弱引用
摘自http://www.jb51.net/article/56924.htm 浅谈C语言中的强符号.弱符号.强引用和弱引用 投稿:hebedich 字体:[增加 减小] 类型:转载 时间:2014- ...
- 浅谈Java语言环境搭建-JDK8
title: 浅谈Java语言环境搭建-JDK8 blog: CSDN data: Java学习路线及视频 1.What's the JDK,JRE JDK(Java Development Kit ...
- C语言变量声明内存分配
转载: C语言变量声明内存分配 一个由c/C++编译的程序占用的内存分为以下几个部分 1.栈区(stack)— 程序运行时由编译器自动分配,存放函数的参数值,局部变量的值等.其操作方式类似于数据结 ...
- 浅谈C语言中断处理机制
一.中断机制 1.实现中断响应和中断返回 当CPU收到中断请求后,能根据具体情况决定是否响应中断,如果CPU没有更急.更重要的工作,则在执行完当前指令后响应这一中断请求.CPU中断响应过程如下:首先, ...
- 浅谈c语言结构体
对于很多非计算机专业来说,c语言课程基本上指针都不怎么讲,更别说后面的结构体了.这造成很多学生对结构体的不熟悉.这里我就浅谈一下我对结构体的认识. 结构体,就是我们自己定义出一种新的类型,定义好之后, ...
- C语言变量声明问题——变量定义一定要放在所有执行语句/语句块的最前面吗?
报错信息:error C2065: 'salary' : undeclared identifier #include <stdio.h> void main(){ printf(&quo ...
- 浅谈C# 匿名变量
每次写博客,第一句话都是这样的:程序员很苦逼,除了会写程序,还得会写博客!当然,希望将来的一天,某位老板看到此博客,给你的程序员职工加点薪资吧!因为程序员的世界除了苦逼就是沉默.我眼中的程序员大多都不 ...
- C语言变量声明加冒号的用法
有些信息在存储时,并不需要占用一个完整的字节, 而只需占几个或一个二进制位.例如在存放一个开关量时,只有0和1 两种状态, 用一位二进位即可.为了节省存储空间,并使处理简便,C语言又提供了一种数据结构 ...
- 浅谈JS的变量提升
JS的解析机制,是JS的又一大重点知识点,在面试题中更经常出现,今天就来唠唠他们的原理.首先呢,我们在我们伟大的浏览器中,有个叫做JS解析器的东西,它专门用来读取JS,执行JS.一般情况是存在作用域就 ...
随机推荐
- 传智播客JavaWeb day11--事务的概念、事务的ACID、数据库锁机制、
1. 什么叫做事务? 2.默认情况下每一条sql语句都是一个事务,然后自动提交事务 ps:如果想多条语句占一个事务,则可以手动设置SetAutoCommit为false 3.关键字 start tr ...
- 联合(union)类型有哪些使用场景
2013-08-22 - 18:17:50 by Gold_Lion 今天突然碰到“联合”这个数据类型了,平时用的不多,实在也想不出来它到底能帮我干嘛?于是google了一下,终于有一个是利用联合( ...
- JDBC连接数据库演示
今天重新学习了JDBC连接数据库,使用的数据库是Oracle,在运行前已经手动建立了一张t_user表,建表信息如下: create table t_user( card_id ) primary k ...
- C# 时间转换 Windows的toFileTimeutc时间转换
项目中需要把时间转为Long型,再转车string,而后又需要转行回标准时间,请用下面这句 DateTime d11t1 = DateTime.FromFileTimeUtc();
- JQuery 控制元素显示隐藏
JS在浏览器里面做调试的时候,先在浏览器里面找到页面代码加上断点来执行.然后根据执行情况来查找部分变量是否能找到,一点一点的排查内容.可以做筛选条件 部分隐藏.默认让部分条件加上.hide 默认隐藏, ...
- cx_oracle 执行cur.execute(sql)提交数据出现 UnicodeEncodeError: 'ascii' codec can't encode character u'\u2122' in position 170
还是中文字符的问题, 解决方法见链接:http://www.oracle.com/technetwork/articles/tuininga-cx-oracle-084866.html import ...
- git submodule 使用
这个是备忘录,原网页: https://medium.com/@porteneuve/mastering-git-submodules-34c65e940407 http://cncc.bingj.c ...
- Lua C++ Binding之Lunar, Luna
服务端引擎的脚本, 我们项目在老端游项目上发展, 采取的是Lua脚本. 当前服务端的发展趋势是瘦引擎, 胖脚本模式, 基本上引擎负责的功能非常少, 主要是网络, 定帧, 定时器, 引擎通过导出相应的接 ...
- ASP.NET Web 应用程序及页面生命周期
以客户端浏览器向 ASP.NET Web 应用程序页面发送请求(Request)为起点,以浏览器收到 Web 服务器的响应(Response)为终点,这一完整的过程被称为"应用程序及页面的生 ...
- jQuery 常用方法经典总结
1.关于页面元素的引用通过jquery的$()引用元素包括通过id.class.元素名以及元素的层级关系及dom或者xpath条件等方法,且返回的对象为jquery对象(集合对象),不能直接调用dom ...