C语言复杂声明的解析
(刘爱贵 Aiguille.LIU)
C语言是一种通用的程序设计语言,它与UNIX系统之间具有非常密切的联系,C语言是在UNIX系统上开发的,并且,无论是UNIX系统本身还是运行其上的大部分程序,都是用C语言编写编写的。C语言很适合用来编写编译器和操作系统以及各种系统底层软件,因此被称为“系统编程语言”,但它同样适合于编写不同领域中的大多数程序。
C语言中,指针的使用非常广泛。与其他方法相比较,使用指针通常可以生成更高效、更紧凑的代码。C语言常常因为声明的语法问题而受到人们的批评,特别是涉及到函数指针的语法。C语言的语法力图使声明和使用相一致。对于简单的情况,C语言的做法是很有效的,但是,如果情况比较复杂,则容易让人混淆,原因在于,C语言的声明不能从左至右阅读,而且使用了太多的圆括号。例如下面的指针声明:
char**argv
int(*daytab)[13]
int*dattab[13]
void*comp()
void(*comp)()
char(*(x())[])()
char(*(*x[3])())[5]
尽管实际中很少用到过于复杂的声明,但是,懂得如何理解和如何使用这些复杂的声明是很重要的,因为有时的确需要这样的声明,尤其对于C语言高级用户来说。另外,很多朋友在求职过程可能也会被问到这样的问题。
C语言参考手册对声明语法作了详细的描述,简化的语法形式如下(dcl为declaration的缩写):
dcl: 前面带有可选的direct-dcl
direct-dcl:name
(dcl)
direct-dcl()
direct-dcl[可选的长度]
简而言之,声明符dcl就是前面可能带有多个*的direct-dcl。direct-dcl可以是name、由一对圆括号括起来的dcl、后面跟有一对圆括号的direct-dcl、后面跟有手方括号括起来的表示可选长度的direct-dcl。上面的语法要用来对C语言的声明进行分析。
右左法则是常用的C语言复杂声明解析方法。右左法则其实并不是C标准里面的内容,它是从C标准的声明规定中归纳出来的方法。C标准的声明规则,是用来解决如何创建声明的,而右左法则是用来解决如何辩识一个声明的,两者可以说是相反的。右左法则的原文如下:
The right-left rule: Start reading the declaration from the innermost parentheses, go right, and then go left. When you encounter parentheses, the direction should be reversed. Once everything in the parentheses has been parsed, jump out of it. Continue till the whole declaration has been parsed.
其大致意思如下:
右左法则:首先从最里面的圆括号看起,然后往右看,再往左看。每当遇到圆括号时,就应该掉转阅读方向。一旦解析完圆括号里面所有的东西,就跳出圆括号。重复这个过程直到整个声明解析完毕。
《C程序设计语言》一书专门讲到复杂声明的解析,并且基于声明符的语法编写了解析程序。经修改和完善后的完整程序如下:
/**//*dcl.c*/
#include<stdio.h>
#include<string.h>
#include<ctype.h>
#defineMAXTOKEN100
enum...{NAME,PARENS,BRACKETS};
voiddcl(void);
voiddirdcl(void);
intgettoken(void);
intgetline(chars[],intlim);
inttokentype;
chartoken[MAXTOKEN];
charname[MAXTOKEN];
chardatatype[MAXTOKEN];
charout[1000];
#defineBUFSIZE1024
charbuf[BUFSIZE];
charline[BUFSIZE];
intbufp=0;
intlinep=0;
intgetch(void)...{
if(bufp>0||(line[linep]!=''))
return(bufp>0)?buf[--bufp]:line[linep++];
else
returnEOF;
}
voidungetch(intc)
...{
if(bufp>=BUFSIZE)
printf("ungetch:toomanycharacters ");
else
buf[bufp++]=c;
}
intmain(intargc,char*argv[])
...{
getline(line,BUFSIZE);
while(gettoken()!=EOF)...{
strcpy(datatype,token);
out[0]='';
dcl();
if(tokentype!=' ')
printf("syntaxerror ");
printf("%s:%s%s ",name,out,datatype);
}
return0;
}
intgettoken(void)
...{
intc;
char*p=token;
while((c=getch())==''||c==' ');
if(c=='(')...{
if((c=getch())==')')...{
strcpy(token,"()");
returntokentype=PARENS;
}else...{
ungetch(c);
returntokentype='(';
}
}elseif(c=='[')...{
for(*p++=c;(*p++=getch())!=']';)
;
*p='';
returntokentype=BRACKETS;
}elseif(isalpha(c))...{
for(*p++=c;isalnum(c=getch());)
*p++=c;
*p='';
ungetch(c);
returntokentype=NAME;
}else
returntokentype=c;
}
intgetline(chars[],intlim)
...{
intc,i;
i=0;
while(--lim>0&&(c=getchar())!=EOF&&c!=' ')
s[i++]=c;
if(c==' ')
s[i++]=c;
s[i]='';
returni;
}
voiddcl(void)
...{
intns;
for(ns=0;gettoken()=='*';)
ns++;
dirdcl();
while(ns-->0)
strcat(out,"pointerto");
}
voiddirdcl(void)
...{
inttype;
if(tokentype=='(')...{
dcl();
if(tokentype!=')')
printf("error:missing) ");
}elseif(tokentype==NAME)
strcpy(name,token);
else
printf("error:expectednameor(dcl) ");
while((type=gettoken())==PARENS||type==BRACKETS)
if(type==PARENS)
strcat(out,"functionreturning");
else...{
strcat(out,"array");
strcat(out,token);
strcat(out,"of");
}
}
程序dcl的核心是两个函数:dcl和dirdcl,它们根据声明符的语法对声明进行分析。因为语法是递归定义的,所以在识别一个声明的组成部分时,这两个函数是相互递归调用的。我们称该程序是一个递归下降语法的分析程序。 另外还有四个函数:
getline():读入声明程序行,由用户从STDIN输入
getch(): 模拟读入一个字符
ungetch():模拟回入一个字符
gettoken():跳过空格与制表符,以查找输入中的下一个token。
使用dcl程序对上面复杂声明的例子进行解析可以得到如下结果:
char**argv
argv:pointertopointertochar
int(*daytab)[13]
daytab:pointertoarray[13]ofint
int*daytab[13]
daytab:array[13]ofpointertoint
void*comp()
comp:functionreturningpointertovoid
void(*comp)()
comp:pointertofunctionreturningvoid
char(*(*x())[])()
x:functionreturningpointertoarray[]ofpointertofunctionreturningchar
char(*(*x[3])())[5]
x: array[3] of pointer to function returning pointer to array[5] ofchar
以上程序的编译和运行环境为:Redhat Linux 2.4 + gcc3.2.3。
参考文献:
1、C程序设计语言. Brian W. Kernighan & Dennis M. Ritche
2、c语言复杂声明的解析 http://hi.baidu.com/cuifenghui/blog/item/9b65e2cde2893d510fb345c4.html
分享到:
相关推荐
cdecl声明解析器,帮助你解析复杂的C语言声明。
因为C语言所有复杂的指针声明,都是由各种声明嵌套构成的。如何解读复杂指针声明呢?
以下是对C语言中的typedef与复杂函数声明问题进行了详细的分析介绍,需要的朋友可以过来参考下
1.21 怎样建立和理解非常复杂的声明?例如定义一个包含N个指向返回指向字符的指针的函数的指针的数组? 11 1.22 如何声明返回指向同类型函数的指针的函数?我在设计一个状态机,用函数表示每种状态,每个函数...
1.21 怎样建立和理解非常复杂的声明?例如定义一个包含N个指向返回指向字符的指针的函数的指针的数组? 1.22 如何声明返回指向同类型函数的指针的函数?我在设计一个状态机,用函数表示每种状态,每个函数都会返回...
1.21 怎样建立和理解非常复杂的声明?例如定义一个包含N个指向返回指向字符的指针的函数的指针的数组? 1.22 如何声明返回指向同类型函数的指针的函数?我在设计一个状态机,用函数表示每种状态,每个函数都会返回一...
C语言所有复杂的指针声明,都是由各种声明嵌套构成的。如何解读复杂指针声明呢?右左法则是一个既著名又常用的方法。
他善于用容易理解的方法和语言说明复杂的概念。许多人认为他开创了计算机书籍贴近大众的新风,为我国的计算机普及事业做出了重要的贡献。 谭浩强教授曾获全国高校教学成果国家级奖、国家科技进步奖,以及北京市政府...
编写一个完整的类C语言编译器是一个复杂的编程项目,涉及多个阶段,包括词法分析、语法分析、语义分析和目标代码生成。以下是一个简化的项目介绍,描述了如何使用Java实现这样一个编译器。 ### 项目介绍: **目标**...
他善于用容易理解的方法和语言说明复杂的概念。许多人认为他开创了计算机书籍贴近大众的新风,为我国的计算机普及事业做出了重要的贡献。 谭浩强教授曾获全国高校教学成果国家级奖、国家科技进步奖,以及北京市政府...
首先,通过应用新研发的用例模型,在不编写任何代码的前提下,可以对c语言语法允许的各种复杂的输入、输出数据快捷准确的创建用例并进行测试。其次,类c语言的脚本的引入,令系统模型能够描述数据之间各种复杂的约束...
使用PLY,可以创建能够解析复杂文本和编程语言的程序。在实现词法分析和语法分析之后,可以进一步进行语义分析,从而对源代码的语义进行检查和操作。 ### 项目介绍: **目标**:使用PLY库实现一个C语言的词法分析器...
在c++中狭义的对象指的是用类,结构,联合等复杂数据类型来声明的变量,如 MyClass myclass,CDialog mydlg,等等。广义的对象还包括用int,char,float等简单类型声明的变量,如int a,char b等等。我在下文提到...
ARP (Address Resolution Protocol)(地址解析協議) 12.IP地址的编码分为哪俩部分? IP地址由两部分组成,网络号和主机号。不过是要和“子网掩码”按位与上之后才能区分哪些是网络位哪些是主机位。 13.用户输入...