1. 編譯器能夠完成的工作是
1. 詞法分析詞法分析器根據詞法規則識別出源程序中的各個記號(token),每個記號代表一類單詞(lexeme)。源程序中常見的記號可以歸為幾大類:關鍵字、標識符、字面量和特殊符號。詞法分析器的輸入是源程序,輸出是識別的記號流。詞法分析器的任務是把源文件的字元流轉換成記號流。本質上它查看連續的字元然後把它們識別為「單詞」。
2. 語法分析語法分析器根據語法規則識別出記號流中的結構(短語、句子),並構造一棵能夠正確反映該結構的語法樹。
3. 語義分析語義分析器根據語義規則對語法樹中的語法單元進行靜態語義檢查,如果類型檢查和轉換等,其目的在於保證語法正確的結構在語義上也是合法的。
4. 中間代碼生成中間代碼生成器根據語義分析器的輸出生成中間代碼。中間代碼可以有若干種形式,它們的共同特徵是與具體機器無關。最常用的一種中間代碼是三地址碼,它的一種實現方式是四元式。三地址碼的優點是便於閱讀、便於優化。
5. 中間代碼優化
優化是編譯器的一個重要組成部分,由於編譯器將源程序翻譯成中間代碼的工作是機械的、按固定模式進行的,因此,生成的中間代碼往往在時間和空間上有很大浪費。當需要生成高效目標代碼時,就必須進行優化。
6. 目標代碼生成
目標代碼生成是編譯器的最後一個階段。在生成目標代碼時要考慮以下幾個問題:計算機的系統結構、指令系統、寄存器的分配以及內存的組織等。編譯器生成的目標程序代碼可以有多種形式:匯編語言、可重定位二進制代碼、內存形式。
7 符號表管理
符號表的作用是記錄源程序中符號的必要信息,並加以合理組織,從而在編譯器的各個階段能對它們進行快速、准確的查找和操作。符號表中的某些內容甚至要保留到程序的運行階段。
8 出錯處理用戶編寫的源程序中往往會有一些錯誤,可分為靜態錯誤和動態錯誤兩類。所謂動態錯誤,是指源程序中的邏輯錯誤,它們發生在程序運行的時候,也被稱作動態語義錯誤,如變數取值為零時作為除數,數組元素引用時下標出界等。靜態錯誤又可分為語法錯誤和靜態語義錯誤。語法錯誤是指有關語言結構上的錯誤,如單詞拼寫錯、表達式中缺少操作數、begin和end不匹配等。靜態語義錯誤是指分析源程序時可以發現的語言意義上的錯誤,如加法的兩個操作數中一個是整型變數名,而另一個是數組名等。
2. c++高手求教 新手問題
不對吧,const靜態類成員可以直接初始化,其他非const的靜態類成員需要在類聲明以外初始化。
http://www.cnblogs.com/fre2technic/archive/2011/03/25/1995044.html看一下這個博客,估計你就懂了
3. c/c++ 編譯器如何區分char[] 與 char*
1、本身 char [] 和char * 就是不同的符號 編譯器自然能識別 。
2、你說的是符號表吧。 符號表主要是給鏈接器用的。
3、關鍵你要理解C/C++程序的存儲分布。
一個由c/C++編譯的程序佔用的內存分為以下幾個部分
1、棧區(stack)—由編譯器自動分配釋放,存放函數的參數值,局部變數的值等。其操作方式類似於
數據結構中的棧。
2、堆區(heap)—一般由程序員分配釋放,若程序員不釋放,程序結束時可能由OS回收。注意它與數據
結構中的堆是兩回事,分配方式倒是類似於鏈表,呵呵。
3、全局區(靜態區)(static)—全局變數和靜態變數的存儲是放在一塊的,初始化的全局變數和靜態
變數在一塊區域,未初始化的全局變數和未初始化的靜態變數在相鄰的另一塊區域。程序結束後由系統
釋放。
4、文字常量區—常量字元串就是放在這里的。程序結束後由系統釋放。
5、程序代碼區
這是一個前輩寫的,非常詳細
//main.cpp
int a=0; //全局初始化區
char *p1; //全局未初始化區
main()
{
int b;棧
char s[]="abc"; //棧
char *p2; //棧
char *p3="123456"; //123456\0在常量區,p3在棧上。
static int c=0; //全局(靜態)初始化區
p1 = (char*)malloc(10);
p2 = (char*)malloc(20); //分配得來得10和20位元組的區域就在堆區。
strcpy(p1,"123456"); //123456\0放在常量區,編譯器可能會將它與p3所向"123456"優化成一個地方。
}
stack:
由系統自動分配。例如,聲明在函數中一個局部變數int b;系統自動在棧中為b開辟空間
heap:
需要程序員自己申請,並指明大小,在c中malloc函數
如p1=(char*)malloc(10);
在C++中用new運算符
如p2=(char*)malloc(10);
但是注意p1、p2本身是在棧中的。
4. C語言程序編譯後產生哪些類型的文件這些文件的作用是什麼
1、以GCC編譯器為例,可以分為四步。
第一步是預處理,包括語法檢查等工作。
gcc
-P
abc.c
第二步由源程序生產匯編語言代碼。
gcc
-S
abc.c
會生成abc.s文件,這個文件里就是匯編代碼。
第三步編譯器生成目標代碼,一個源文件生成一個目標代碼。
gcc
-c
abc.c
會生成abc.o
第四步連接器從目標代碼生成可執行文件。
gcc
abc.o
2、目標代碼包括機器碼和符號表(函數及變數名)。連接器的主要作用是通過符號表在庫文件和其他模塊中找到在目標代碼中引入或未定義的符號(函數及變數名),將幾個目標代碼合成可執行文件。
5. 關於C語言指針,打破砂鍋問到底
程序編譯階段,編譯器會生成一個符號表,裡面記錄的是每一個變數的相關信息,比如,類型,分配的地址等等。
編譯的時候會查詢符號表來編譯,舉個例子:
int *p;
假如符號表中記錄的 p 所在的地址是1234;
p=&a;
當遇到這句的時候,編譯器會查找符號表,找到符號a的信息,把它的地址記錄下來(假設為4567),接著找到p的地址為1234,然後把上面那句話翻譯為:*(int *)(1234)=4567;(意在說明問題,實際的產生的並非這樣的代碼,但原理是類似的)
,(int *)&&p,(int *)&&&p這些語句都是無效的。
想要深入了解,可以看看編譯原理的符號表內容
希望對你有幫助
6. 定義語法樹和符號表的數據結構
為了維持靜態作用域的程序里各個名字的軌跡,編譯器需要依靠一種稱為符號表的數據結構。從最基本的層次上看,符號表就是一個字典:它把名字映射到編譯器已知的有關信息。這里最基本的操作是把一個新映射關系(名字對象約束)放入表裡,以及(非破壞性的)用一個給定名字去提取映射下的信息,以後我們把
這兩個操作分別稱為insert和lookup。大部分語言里的靜態作用域規則還提出了另外的復雜性,它們要求在程序里的不同部分有不同的引用環境。為了處理作用域規則,我們可能希望簡單增加一個remove操作。由於編譯器在語義分析階段要從頭到尾掃描代碼,這樣它就可以在某個作用域開始時插入新約束,在作用域最後撤銷它們。但是,存在一些因素使這種直接做法並不實際。
¨ 在許多有著嵌套作用域的語言里,內層聲明的效果可以遮蔽外層聲明,這就意味著符號表必須有能力為一個給定名字保存任意數目的映射。lookup操作必須返回最內層的映射,到作用域結束時還必須使外層映射重新變成可見的。
¨ 類Algol語言里的記錄(結構)具有某種作用域性質,但卻又不享有作用域那樣的良好嵌套結構。當語義分析器看到一個記錄聲明時,它就必須記下各個記錄域的名字(它們也是遞歸的,因為記錄可以嵌套)。在這種聲明結束時,各個域的名字又必須變成不可見的。然而,在此之後,一旦這一記錄類型的某個變數出現在程序的正文里(例如在my_rec.field_name),在引用中位於圓點之後的部分,這些域名又必須立即重新變成可見的。在Pascal和另一些有with語句的語言里,記錄域的名字還應該在多個語句的上下文里變成可見的。
¨ 某些時候一些名字有可能在它們被聲明之前使用,即使在類Algol語言里情況也如此。舉例說,Algol 60和Algol 68都允許標號的向前引用。Pascal避免了這種情況,它要求標號必須在作用域開始處聲明,但還是允許指針聲明的向前引用:
type
7. 西門子S7-300/400PLC的編程軟體中,符號表的用處,和其導出導入的功能的作用。
符號表的用途在於給IO變數或數據定義一個名稱,方便在編寫程序及閱讀程序的時候理解其含義。比如I0.0,定義為P0101_RUN,注釋為提升泵運行信號,可以按自己熟悉的命名格式。其導入導出可以用EXCEL或其他工具快速定義符號。
變數表是用來監控相應變數在線狀態的,可以根據不同的調試要求,生成多個變數表。變數表是不會下載到PLC裡面的。
舉個最簡單的例子吧,控制一個閥門打開及關閉,有幾個條件:I0.0 集中,I0.1打開,I0.2關閉,I0.3開到位,I0.4關到位,I0.5故障(比如過力距),Q0.0打開輸出,Q0.1關閉輸出。
(7)符號表在編譯器中的作用擴展閱讀:
美國汽車工業生產技術要求的發展促進了PLC的產生,20世紀60年代,美國通用汽車公司在對工廠生產線調整時,發現繼電器、接觸器控制系統修改難、體積大、雜訊大、維護不方便以及可靠性差,於是提出了著名的「通用十條」招標指標。
1969年,美國數字化設備公司研製出第一台可編程式控制制器(PDP-14),在通用汽車公司的生產線上試用後,效果顯著;
1971年,日本研製出第一台可編程式控制制器(DCS-8);1973年,德國研製出第一台可編程式控制制器;1974年,我國開始研製可編程式控制制器:1977年,我國在工業應用領域推廣PLC。
最初的目的是替代機械開關裝置(繼電模塊)。然而,自從1968年以來,PLC的功能逐漸代替了繼電器控制板,現代PLC具有更多的功能。其用途從單一過程式控制制延伸到整個製造系統的控制和監測。
8. 如何建立符號表
Symbol Tables
為了維持靜態作用域的程序里各個名字的軌跡,編譯器需要依靠一種稱為符號表的數據結構。從最基本的層次上看,符號表就是一個字典:它把名字映射到編譯器已知的有關信息。這里最基本的操作是把一個新映射關系(名字對象約束)放入表裡,以及(非破壞性的)用一個給定名字去提取映射下的信息,以後我們把
這兩個操作分別稱為insert和lookup。大部分語言里的靜態作用域規則還提出了另外的復雜性,它們要求在程序里的不同部分有不同的引用環境。為了處理作用域規則,我們可能希望簡單增加一個remove操作。由於編譯器在語義分析階段要從頭到尾掃描代碼,這樣它就可以在某個作用域開始時插入新約束,在作用域最後撤銷它們。但是,存在一些因素使這種直接做法並不實際。
¨ 在許多有著嵌套作用域的語言里,內層聲明的效果可以遮蔽外層聲明,這就意味著符號表必須有能力為一個給定名字保存任意數目的映射。lookup操作必須返回最內層的映射,到作用域結束時還必須使外層映射重新變成可見的。
¨ 類Algol語言里的記錄(結構)具有某種作用域性質,但卻又不享有作用域那樣的良好嵌套結構。當語義分析器看到一個記錄聲明時,它就必須記下各個記錄域的名字(它們也是遞歸的,因為記錄可以嵌套)。在這種聲明結束時,各個域的名字又必須變成不可見的。然而,在此之後,一旦這一記錄類型的某個變數出現在程序的正文里(例如在my_rec.field_name),在引用中位於圓點之後的部分,這些域名又必須立即重新變成可見的。在Pascal和另一些有with語句的語言里,記錄域的名字還應該在多個語句的上下文里變成可見的。
¨ 某些時候一些名字有可能在它們被聲明之前使用,即使在類Algol語言里情況也如此。舉例說,Algol 60和Algol 68都允許標號的向前引用。Pascal避免了這種情況,它要求標號必須在作用域開始處聲明,但還是允許指針聲明的向前引用:
type
company = record
CEO : ^person; (* forward reference *)
...
end;
person = record
employer : ^company;
...
end;
¨ Pascal和其他語言都允許子程序的向前聲明,以便支持相互遞歸:
procere Q (A, B : integer); forward;
procere P (A, B : integer);
begin
...
Q (3, 4);
...
end;
procere Q; (* parameters are not repeated in Pascal *)
begin
...
P (4, 5);
...
end;
在看到這段代碼里的向前聲明時,語義分析器必須記住Q的參數,以便後面可以在Q的體里使它們重新變成可見的,但在此期間又必須使它們成為不可見的。這種操作類似於記住記錄域的名字。
¨ 雖然我們有可能希望在作用域結束時忘記有關的名字,甚至回收這些名字在符號表裡占據的空間,但有關它們的信息仍需要保存起來,以便符號糾錯系統(symbolic debugger)使用。這種糾錯系統是非常有用的工具,用戶可以藉助它方便地操縱程序,如啟動程序,停住它,讀出或者修改程序里的數據等等。為了分析來自用戶的高級名字(例如,要求列印出my_firm^.revenues[1999] 的值),符號糾錯程序必須能訪問編譯器的符號表。為了使符號表在運行時也可以用,編譯器通常會把這個表保存到最後的機器語言程序里的某個隱蔽的部分。
靜態作用域的大部分變化都可以通過擴充基本符號表的方式處理,通過增加一對enter_scope和leave_scope操作維持可見性的軌跡。任何東西都不會從符號表裡刪除,在整個編譯階段所有的結構都保留著,最後還要為糾錯系統使用而保存起來。帶有可見性處理的符號表可以以多種不同方式實現,下面描述的方式歸功於LeBlanc和Cook [CL83]。
在遇到每個作用域時賦給它一個序列號。給定最外層的作用域(其中包含著預定義的標識符)編號0,包含用戶定義全局名字的作用域給以編號1。其他作用域按遇到它們的順序進行編號。所有的編號互不相同,它們並不表示詞法嵌套的層次,但也有一點,嵌套於內部的子程序的編號自然會大於它們的外圍作用域的編號。
所有的名字都被放入一個大的散列表裡,以名字作為關鍵碼,無論其作用域如何。表裡的每項都包含一個符號名,其類屬(變數、常量、類型、過程、域名字、參數等等),作用域編號,類型(一個指向另一符號表項的指針),以及另一些特定類屬所擁有的信息。
除了這一散列表之外,符號表還包含一個作用域堆棧,它按順序指明組成當前引用環境的所有作用域。在語義分析器掃描程序的過程中,在進入或退出程序時分別壓入或者彈出這個堆棧。作用域堆棧的項里包含著作用域編號,指明這一作用域是否為閉的,有些情況下還可以有另外一些信息。
圖3.13 LeBlanc-Cook符號表的lookup演算法。
當需要到表裡查找名字時,我們會順著某個適當的散列表鏈向下找,這樣就會找到要找的名字所對應的一些項。對於每個匹配項,我們都向下掃描作用域堆棧,看看這個項所在的作用域是否可見。這種堆棧查看的深度不應超過最上面的閉作用域。要把導入項和導出項變為在它們的作用域之外可見的,方法就是在表裡建立另外的項,讓這些項里包含著指向實際項的指針。對於所有帶有作用域編號0的項,我們都不需要去檢查作用域堆棧,因為它們是滲透性的。圖3.13里是lookup演算法的偽代碼。
圖3.14的右下角是一個Mola-2程序的梗概,圖中其餘部分展現的是在過程P2里with語句處的引用環境的符號表配置情況。作用域堆棧里包含4個項,分別表示那個with語句,過程P2,模塊M和全局作用域。with語句的作用域指明了在這一特定作用域里的(域)名字屬於哪個記錄變數。最外面的滲透性作用域沒有顯式表示。
圖3.14 一個Mola-2例子程序的LeBlanc-Cook符號表。作用域堆棧表示在過程P2里with語句的引用環境。為清楚起見,許多指向符號表裡對應於integer和real的項都用帶括弧的 (1) 和 (2) 表示,沒有畫出箭頭。
因為這里的散列表以名字作為關鍵碼,特定名字的所有項都出現在同一個散列鏈里。在這個例子里,散列沖突導致A2、F2和T出現在同一個鏈里。變數V和I(M的I)有另外的項,使它們跨過閉作用域M的邊界後仍為可見的。當我們處於P2里時,對於I的查找操作將找到P2的I,M的I里的兩個項都不可見。類型T的項指明了在with語句期間放入作用域堆棧的作用域編號。每個子程序的項里包含了一個頭指針,指向子程序參數的鏈接表,以便做調用分析時使用(圖中沒有給出這些鏈的一些鏈接)。在代碼生成過程中,許多符號表項還可能包含另外的域,表示例如對象大小和運行時地址等等信息。
圖片信息,看參考資料。。。
9. 符號表和抽象語法樹是什麼關系兩者在編譯器設計中是否必需
一般的編譯器可能包含下面這些模塊:
1, 詞法分析器:
輸入: 源代碼
輸出: token
2, 語法分析器:
輸入: token
輸出: AST
在這個過程中, 可以識別出不符合語法規則的語句, 就可以報syntax錯誤, 如果有syntax錯誤, 編譯結束
3, 語義分析器:
輸入: AST
輸出: 無
在這個過程中, 根據語言的語義規則來識別語義錯誤, 要識別語義錯誤 就必須編譯AST, 因為是樹的遍歷, 假如你先遍歷到了int a 這個節點, 接著又遍歷到了一個表達式a = 4這個節點, 你需要檢查變數a有沒有聲明啊, 變數a和4的類型批不匹配呢? 這時你如果沒有保存變數a的信息, 那麼你怎麼檢查? 所以就需要符號表來保存這些信息了.
4, 代碼優化:
最簡單的就是常量折疊優化了, 比如: a = 1 + 2 這個語句可以直接換成: a = 3了, 也就是說在編譯階段就把一些必要的運算先計算完成, 在程序運行的時候就不需要計算這些了, 就提高了程序的運行效率. 這部分是最復雜的了, 還有各種各樣各樣的優化
5, 代碼生成:
輸入: AST
輸出: 可以是虛擬機代碼, 可以是本地匯編代碼