C語言複雜聲明的解析

C語言複雜聲明的解析


  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>

#define MAXTOKEN 100

enum { NAME, PARENS, BRACKETS };

void dcl(void);
void dirdcl(void);
int  gettoken(void);
int  getline(char s[], int lim);
int  tokentype;
char token[MAXTOKEN];
char name[MAXTOKEN];
char datatype[MAXTOKEN];
char out[1000];

#define BUFSIZE  1024
char buf[BUFSIZE];
char line[BUFSIZE];
int bufp = 0;
int linep = 0;

int getch(void){
  
if (bufp > 0 || (line[linep] != ''))
    
return (bufp > 0? buf[--bufp] : line[linep++];
  
else
    
return EOF;
}

void ungetch(int c)
{
  
if (bufp >= BUFSIZE) 
    printf(
"ungetch: too many characters ");
  
else
   buf[bufp
++= c;
}


int main(int argc, char* argv[]) 
{
  getline(line, BUFSIZE);
  
while (gettoken() != EOF) {
    strcpy(datatype, token);
    
out[0= '';
    dcl();
    
if (tokentype != ' ')
      printf(
"syntax error ");
    printf(
"%s: %s %s ", name, out, datatype);
  }

  
return 0;
}


int gettoken(void
{
  
int c;
  
char *= token;

  
while ((c = getch()) == ' ' || c == ' ') ;
  
if (c == '('{
    
if ((c = getch()) == ')'{
      strcpy(token, 
"()");
      
return tokentype = PARENS;
    }
 else {
      ungetch(c);
      
return tokentype = '(';
    }

  }
 else if (c == '['{
    
for (*p++ = c; (*p++ = getch()) != ']';)
      ;
    
*= '';
    
return tokentype = BRACKETS;
  }
 else if (isalpha(c)) {
    
for (*p++ = c; isalnum(c = getch());)
      
*p++ = c;
    
*= '';
    ungetch(c);
    
return tokentype = NAME;
  }
 else
    
return tokentype = c;
}


int getline(char s[], int lim)
{
  
int c, i;

  i 
= 0;
  
while (--lim > 0 && (c = getchar()) != EOF && c != ' ')
    s[i
++= c;
  
if (c == ' ')
    s[i
++= c;
  s[i] 
= '';
  
return i;
}


void dcl(void)
{
  
int ns;

  
for (ns = 0; gettoken() == '*';)
    ns
++;
  dirdcl();
  
while (ns-- > 0)
    strcat(
out" pointer to");
}


void dirdcl(void)
{
  
int type;

  
if (tokentype == '('{
    dcl();
    
if (tokentype != ')')
      printf(
"error: missing ) ");
  }
 else if (tokentype == NAME)
    strcpy(name, token);
  
else
    printf(
"error: expected name or (dcl) ");
  
while ((type = gettoken()) == PARENS || type == BRACKETS)
    
if (type == PARENS)
      strcat(
out" function returning");
    
else {
      strcat(
out" array");
      strcat(
out, token);
      strcat(
out" of");
    }

}
程序dcl的核心是兩個函數:dcl和dirdcl,它們根據聲明符的語法對聲明進行分析。因爲語法是遞歸定義的,所以在識別一個聲明的組成部分時,這兩個函數是相互遞歸調用的。我們稱該程序是一個遞歸下降語法的分析程序。 另外還有四個函數:
  getline():讀入聲明程序行,由用戶從STDIN輸入
  getch(): 模擬讀入一個字符
  ungetch():模擬回入一個字符
  gettoken():跳過空格與製表符,以查找輸入中的下一個token。

  使用dcl程序對上面複雜聲明的例子進行解析可以得到如下結果:
char **argv
argv:  pointer to pointer to 
char

int (*daytab)[13]
daytab:  pointer to array[
13] of int

int *daytab[13]
daytab:  array[
13] of pointer to int

void *comp()
comp:  function returning pointer to 
void

void (*comp)()
comp:  pointer to function returning 
void

char (*(*x())[])()
x:  function returning pointer to array[] of pointer to function returning 
char
   char (*(*x[3])())[5]
x:  array[3] of pointer to function returning pointer to array[5] of  
char
以上程序的編譯和運行環境爲:Redhat Linux 2.4 + gcc3.2.3。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章