在語法分析裡,最主要的組成部份是宣告分析,並且這是
c語言編譯器最複雜的組成部分。由於任何變數都需要宣告,那麼怎麼樣知道這個變數宣告是合法的呢?現在帶著這個問題去分下面的**。
為了理解**的工作,先來看前面的例子裡的第一行有效**:
typedef unsigned int size_t;
在這句語句裡,使用型別定義關鍵字來宣告了乙個無符號整數的型別
size_t
,為了識別這句語句的語法,那麼最開始的是型別關鍵字,它相當於儲存型別。接著是無符號、整型,最後才是標識
id。其實上面這句語句也可能有這種形式,如下:
typedef int size_t;
那麼上面這句就上面那句少了乙個無符號的說明。
要分析這種宣告,那麼就需要乙個函式來把這些屬性整理出來,並斷它的合法性。
lcc裡的宣告分析是在函式
decl
裡呼叫函式
specifier
來實現的:
#001 //說明
#002 static type specifier(int *sclass)
#003
#012 第
2行裡輸入了獲取宣告的儲存型別引數
sclass。
乙個宣告變數有可能出現的說明符就有以下幾種:
儲存型別、常量說明、符號說明、型別大小、型別、是否刪除。
比如像這些語句:
auto int a;
register int iloop;
auto unsigned int nret;
static unsigned int g_nret;
const unsigned int cnstret; 第
4行裡就是定義上面幾種說明的型別變數儲存。 第
5行裡定義了返回型別儲存變數。 第
7行是把所有型別說明初始化為
0值,表示沒有出現這種說明。 第
8行到第
11行是把儲存型別設定為自動型別。由於在
c語言裡,所有變數宣告如果沒有明確地指明儲存型別時,預設就是自動型別
auto。
下面就通過迴圈來分析出
6種說明:
#013 for (;;)
#014
#054 p = &size;
#055 t = gettok();
#056 break;
上面識別
long
型別和long long的64
位的型別。
#057 case short:
#058 p = &size;
#059 t = gettok();
#060 break;
#061 case void:
#062 case char:
#063 case int:
#064 case float:
#065 case double:
#066 p = &type;
#067 ty = tsym->type;
#068 t = gettok();
#069 break;
上面這些都是簡單型別的識別。
#070 case enum:
#071 p = &type;
#072 ty = enumdcl();
#073 break;
上面是列舉型別識別,呼叫函式
enumdcl
進行型別的分配。後面再仔細地討論怎麼樣處理列舉型別的成品。
#074 case struct:
#075 case union:
#076 p = &type;
#077 ty = structdcl(t);
#078 break;
上面結構定義和聯合的識別,這也是比較複雜的型別,所以也呼叫
structdcl
來進一步處理結構體。
#079 case id:
#080 if (istypename(t, tsym) && type == 0
#081 && sign == 0 && size == 0)
#082
#098
#099 p = &type;
#100 t = gettok();
#101 }
#102 else
#103
#106 break;
當把所有的說明符分析完成後,最後肯定是剩下乙個
id,如果不是就有可能出錯的。 在第
80行到第
101行裡處理
id是自己定義的型別處理,比如用
typedef
定義的id
型別,就需要在那裡識別出型別的屬性。
如果這個
id是變數,就會在第
104行設定為空,並且在後面的第
111行到第
114行裡跳出來
for迴圈。
#107 default:
#108 p = null;
#109 }
#110
#111 if (p == null)
#112
#115
#116 if (*p)
#117
#120
#121 *p = tt;
#122 }
上面在第
113行裡跳出了
for迴圈,意味著整宣告已經分析完成,接著就是把所有分析出來的說明符組成屬性結構,儲存了到相應的符號表裡。
#123
#124 if (sclass)
#125 *sclass = cls;
#126
#127 if (type == 0)
#128
#132 第
124行到第
125行儲存儲存型別返回給呼叫函式。 第
127行到第
131行是設定型別為預設值。
#133 if (size == short && type != int
#134 || size == long+long && type != int
#135 || size == long && type != int && type != double
#136 || sign && type != int && type != char)
#137
#140 第
133行到第
136行裡都判斷宣告組合是否合法,如果不合法的組合,就需要提示出錯。
#141 if (type == char && sign)
#142
#145 else if (size == short)
#146
#149 else if (size == long && type == double)
#150
#153 else if (size == long+long)
#154
#159 else if (size == long)
#160
#163 else if (sign == unsigned && type == int)
#164
#167 第
141行到第
166行就是根據符號和型別來判斷宣告的型別,由於型別的大小不同,符號位不同,而組成不同的型別。這些型別都是
c編譯器預先定義好的。不知道你還記起最開始的型別初始化沒有?如果不記起,就回過頭去看看。
#168 if (cons == const)
#169 ty = qual(const, ty);
#170
#171 if (vol == volatile)
#172 ty = qual(volatile, ty);
#173
#174 return ty;
#175 } 第
168行把型別新增常量屬性。 第
171行是型別新增不可刪除屬性。 第
174行就把宣告的型別返回給呼叫函式。
通過上面的**,就可以把
c裡的宣告分析出來,如果不合法的宣告就會出錯。如果合法的宣告,就返回相應的型別屬性。只要有了宣告的型別屬性,那麼乙個變數的定義就完全了解了,也就是說它的語義已經確定下來。只剩下了乙個問題沒有解決?這個問題是什麼呢?下一次再告訴你吧,這一次分析的**夠長的了。
LCC編譯器的源程式分析 10 宣告型別
上一次把宣告的說明符已經分析得很清楚,也就是把 c的變數和函式宣告都已經了解了。最後還剩下乙個問題沒有解決,這個問題就是宣告後面的 id是變數呢?還是函式?或者是指標?為了識別後面的 id,下面來看乙個例子。如下的語句 typedef unsigned int size t 這是第一行處理的 它通過...
LCC編譯器的源程式分析 14 結構型別的宣告
以前都是簡單型別的識別和語法分析,現在來分析結構的宣告,它是比較複雜的一種資料型別,但結構在編寫程式中使用是非常多的。由於程式的方程式就是 資料結構 演算法 程式現在物件導向的方程式是 資料結構 演算法 物件 物件 物件 程式 由上面的公式,就可以看出程式中的資料結構是非常重要的,無論是物件導向的程...
LCC編譯器的源程式分析 17 引數變數的宣告
函式裡的引數變數個數不固定,因此也需要檢查這些引數的名稱是否相同,還需要檢查型別的合法性。現在就來分析上次提到的函式 dclparam 它的 如下 001 引數型別宣告處理 002 static symbol dclparam int sclass,char id,type ty,coordinat...