前面我們已經學會了如何理解聲明:https://www.cnblogs.com/surplusvalue/p/12123398.html
事實上,在我們讀源碼的時候,或許也會遇到錯綜復雜的聲明語句,為什么不寫一個程序幫助我們理解呢?接下來我們將編寫一個能夠分析C語言的聲明並把它們翻譯成通俗語言的程序。為了簡單起見,暫且忽略錯誤處理,而且在處理結構、枚舉和聯合時只簡單地用“struct”、“enum”和“union”來代表它們的具體內容。最后,這個程序假定函數的括號內沒有參數列表(實際上我們在分析的時候,參數列表也被忽略了)。
主要的數據結構是一個堆棧,我們從左向右讀取,把各個標記依次壓入堆棧,直到讀到標識符為止。然后我們向右讀入一個標記,也就是標識符右邊的那個標記。接着觀察標識符左邊的那個標記(需要從堆棧中彈出)。數據結構大致如下:
struct token { char type; char string[MAXTOKENLEN]; }; /* 保存第一個標識之前的所有標記 */ struct token stack[MAXTOKENS]; /* 保存剛讀入的那個標記 */ struct token t;
偽碼如下:
實用程序---------- classify_string(字符串分類) 查看當前標記, 通過t.type返回一個值,內容為“type(類型)”,“qualifier(限定符)”或“identifier(標識符)” gettoken(取標記) 把下一個標記讀入t.string 如果是字母數字組合,調用classify_string 否則,它必是一個單字符標記,t.type=該標記;用一個null結束t.string read_to_first_identifier(讀至第一個標識符) 調用gettoken,並把標記壓入到堆棧中,直到遇見第一個標識符。 Print“identifier is (標識符是)”,t.string 繼續調用gettoken
解析程序----------
deal_with_function_args(處理函數參數)
當讀取越過右括號‘)’后,打印“函數返回”
deal_with_arrays(處理函數數組)
當你讀取“[size]”后,將其打印並繼續向右讀取。
deal_with_any_pointers(處理任何指針)
當你從堆棧中讀取“*”時,打印“指向...的指針”並將其彈出堆棧。
deal_with_declarator(處理聲明器)
if t.type is '[' deal_with_arrays
if t.type is '(' deal_with_function_args
deal_with_any_pointers
while 堆棧里還有東西
if 它是一個左括號'('
將其彈出堆棧,並調用gettoken;應該獲得右括號')'
deal_with_declarator
else 將其彈出堆棧並打印它
主程序----------
main
read_to_first_identifier
deal_with_declarator
代碼如下:
#pragma warning( disable : 4996) #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <string.h> #include <ctype.h> #include <stdlib.h> #define MAXTOKENS 100 #define MAXTOKENLEN 64 //標識符identifier,修飾詞qualifier,類型type(具體的例子見classify_string函數) enum type_tag { IDENTIFIER, QUALIFIER, TYPE }; struct token { char type; //詞素的類型 char string[MAXTOKENLEN]; //保存詞素的內容 }; int top = -1; //棧頂指針 //從左到右依次讀取,把各個標記依次壓入堆棧;保存第一個標識之前的所有標記的堆棧 struct token stack[MAXTOKENS]; struct token t; //保存剛讀入的那個標記; //出棧和入棧操作 #define pop stack[top--] #define push(s) stack[++top]=s enum type_tag classify_string(void) //推斷標識符的類型 { char *s = t.string; if (!strcmp(s, "const")) {//復習strcmp,兩字符串相同,返回0 strcpy(s, "read-only"); return QUALIFIER; } if (!strcmp(s, "volatile")) return QUALIFIER; if (!strcmp(s, "void")) return TYPE; if (!strcmp(s, "char")) return TYPE; if (!strcmp(s, "signed")) return TYPE; if (!strcmp(s, "unsigned")) return TYPE; if (!strcmp(s, "short")) return TYPE; if (!strcmp(s, "int")) return TYPE; if (!strcmp(s, "long")) return TYPE; if (!strcmp(s, "float")) return TYPE; if (!strcmp(s, "double")) return TYPE; if (!strcmp(s, "struct")) return TYPE; if (!strcmp(s, "union")) return TYPE; if (!strcmp(s, "enum")) return TYPE; return IDENTIFIER; //上述結構都不是的情況,就是標識符 } void gettoken(void) // 讀取下一個標記到 "t" { char *p = t.string; // 略過空白字符 while ((*p = getchar()) == ' '); if (isalnum(*p)) { // 讀入的標識符以A-Z, 0-9開頭 while (isalnum(*++p = getchar())); ungetc(*p, stdin); *p = '\0'; t.type = classify_string(); return; } if (*p == '*') { strcpy(t.string, "pointer to"); t.type = '*'; return; } t.string[1] = '\0'; t.type = *p; return; } // 理解所有分析過程的代碼段 void read_to_first_identifier() { gettoken(); //把標記壓入到堆棧,直到遇見第一個標識符 while (t.type != IDENTIFIER) { //只要沒有遇見標識符,就將t壓棧,繼續取標記,直到遇見第一個標識符 push(t); gettoken(); } printf("%s is ", t.string); //這條分析語句的主語是while循環后得到的標識符 gettoken(); //繼續向右讀入一個標記,也就是標識符右邊的標記 } void deal_with_arrays() { while (t.type == '[') { printf("array "); gettoken(); // 數字或 ']' if (isdigit(t.string[0])) { printf("0..%d ", atoi(t.string) - 1); gettoken(); // 讀取 ']' } gettoken(); // 讀取 ']' 之后的再一個標記 printf("of ");//這個數組是: } } void deal_with_function_args() { while (t.type != ')') { gettoken();//忽略函數的參數列表 } gettoken();//讀到和左括號匹配的右括號,再讀括號后的一個標記 printf("function returning "); } void deal_with_pointers() { while (stack[top].type == '*') { printf("%s ", pop.string);//* 號出棧,輸出pointer to } } void deal_with_declarator() { // 處理標識符之后可能存在的數組或函數 switch (t.type) { case '[': deal_with_arrays(); break; case '(': deal_with_function_args(); } //觀察標識符左邊的標記 deal_with_pointers(); // 處理在讀入到標識符之前壓入到堆棧中的符號 while (top >= 0) { //直到棧空 if (stack[top].type == '(') { pop; gettoken(); // 讀取 ')'之后的符號 deal_with_declarator();//這個可以對着例子學習,如果這對括號后面還有標記就繼續處理 } else { printf("%s ", pop.string); } } } int main() { // 將標記壓入堆棧中,直到遇見標識符 read_to_first_identifier(); // 處理標識符右邊的那個標記 deal_with_declarator(); printf("\n"); return 0; }
運行結果:
錯誤解決
觸發斷點的解決辦法 https://blog.csdn.net/leowinbow/article/details/82380252
