运行时存储空间组织
??對于存儲空間這章不是課程的重點,老師講授的內容比較少。不過還是做一點兒整理,供以后復習參考使用。
??學習本章的重點在于了解不同情況下的內存分配策略,大致分為三種:靜態分配、動態棧式分配、動態堆式分配。對于棧式只介紹一些很簡單的,對于堆式沒有介紹。其實這樣也沒有什么考點,權當擴充知識,了解在編譯過程中內存的變化情況。
??學習存儲分配,有幾個基本的概念是要了解的(不一定要背下來,至少說出來要知道是啥吧~):
??這里面提到一個概念叫生存期,之前學高級語言的時候都講變量都有作用域。那這兩個有什么區別呢?生存期是有效時間長短,作用域是有效作用范圍。有點兒像時間空間的感覺。生存期是時間,作用域是空間。
??無論是變量還是函數,都要有一個名字。這個名字指的不是變量本身的值,而是變量存儲的地址。在訪問這個變量或函數的時候,根據地址去取值。(這里我有一個疑惑:C語言中指針即是地址,如果上面的說法正確的話,對于int a;來說,a是變量名,是元素a存儲的地址,a應該是一個指針。那和 int *a有什么區別呢?)我對這個問題自己思考了一下:在int a中這個a并沒有從存儲空間的角度考慮,只是代表了一個變量名字而已。只需要明白可以通過地址找到變量值這個思想。
??調用函數的時候要傳遞參數,在主調函數中的叫實參,被調函數中的叫形參。那 形參和實參間是如何實現傳遞的呢?主要有三種方式:傳地址、傳值、傳名。
??顧名思義,傳地址就是把參數地址送入被調函數,被調函數可根據此地址進行訪問,即被調函數要拿出空間A存放該地址B,實際使用時訪問A,取出B,按照B去拿C。(好繞!)。
??傳值是把參數值送入被調函數,在被調函數中需要分配空間存儲參數值,到時候取值即可。傳名這個聽的時候比較少,這個就是在傳遞參數的時候,形參和實參之間有一一對應的關系。前面兩種當函數被調用的時候,內容就已經傳遞過去了。
??在傳名中,直到調用該形參的時候才會去對應查找其實參。整體感覺有點像一個程序,使用時調用。所以也叫參數子程序。不過傳名幾乎不用,書上說只用于在ALGOL里面,了解一下就可以了。
??現在想一想,整個程序編譯運行過程中。都要存儲哪些內容?一是目標代碼、二是數據信息、三是過程中調用的控制信息。其實還是比較好理解的,存儲生成的目標代碼,機器才能一步一步的執行指令進行調用。數據信息包括各種變量、過程中的控制信息主要是指各個層次之間的嵌套關系,誰調用誰應該心里有數。
??將以上三種內容落實到具體就是活動記錄,下面介紹下活動記錄的定義:把過程的一個活動所需要的信息組織成一塊連續的存儲單元,稱為活動記錄。常以表格的方式來表示,以Pascal語言為例:它的活動記錄是下面這樣的:
??活動記錄大致可以分為三類:連接數據、形式單元、局部數據區。連接數據包括靜態鏈、動態鏈(后面會詳細介紹啥是動態鏈,靜態鏈)、返回地址。局部數據區包括臨時單元(中間代碼生成的),內情向量(數組的首地址即大小范圍等信息),局部向量(子函數內聲明的變量),形式單元(調用函數時傳遞的形參)。
??下面介紹下三種存儲分配策略:靜態存儲分配,棧式存儲分配,堆式存儲分配。其實理解這三者有一個很簡單的方式(當然,簡單意味著不是很嚴謹~)靜態存儲分配就是指程序被預先分配的空間,沒有進行動態分配,編譯時也不會附加任何空間。棧式存儲分配是系統自動進行空間分配,最明顯的例子就是遞歸算法。程序結束,地址空間釋放。堆式存儲分配的特點是由用戶主動劃分,比如C++語言中的new函數,同樣可以進行歸還,也就是C++中的delete。下面更為詳細的介紹下三者:
靜態存儲分配:
??靜態存儲分配要滿足如下三個條件:
??在靜態存儲分配里包括一些聲明的變量空間的分配,在前面的文章中介紹過在做中間代碼生成的時候,會引入大量的中間變量(雖然在最后的優化過程都會去除,但也要為其分配空間)。中間變量的大部分會被消除,如果為它們的每一個都分配空間顯得有點兒浪費,所以選擇了一種處理方式:
??有的時候,中間變量的生成之后,下一條語句就使用掉了,后面也不會再出現,對于這種情況也設定了一種機制:
??實例如下:
棧式存儲分配
??關于棧式存儲分配只介紹了簡單的并且以C語言作為示范。棧式存儲分配都在棧空間里完成,所以需要進行入棧管理。每個過程都以活動記錄作為基本單位,過程發生活動記錄入棧,過程結束出棧。
??那靜態存儲和棧式存儲有什么區別呢(棧式相比于靜態)?
??舉一個抽象一點兒的例子:
??在這個例子中也不是說固定的子程序的活動記錄向上增長,這個取決于棧頂,不斷向下壓棧。
??那棧式空間中C語言的活動記錄是什么樣的呢?
??上圖的sp指的是stack pointer,即棧頂指針top。帶個老字就是上一層的。
??除了簡單的棧式實現,還有一種是嵌套過程語言的棧式實現。什么叫嵌套過程呢?就是指在一個函數中,調用了另一個函數(該函數有可能繼續調用其他函數,但不能相互調用引發死循環,需要是合理的調用)。那這里有一個什么問題呢?就是變量使用的問題。在嵌套過程中,內層是可以使用外層的變量的。有一種極端情況,內外層變量名相同,這時會發生作用域的覆蓋。內層調用的是內層,會覆蓋掉外層。不考慮極端情況,在剛才的介紹中了解到每一層在棧中以活動記錄為單位進行存儲,那內層是如何訪問到外層的活動記錄呢?這部分就是主要解決這個問題的!
??解決的方式有兩種:靜態鏈或Display表。
??靜態鏈:
??靜態鏈的思想就是每層活動記錄保留上一層的sp,這樣如果想訪問外層變量就可以順著sp向上查找。這種方式固定增加了一個sp空間,是靜態的方式。其活動記錄如下:
??解釋下里面為啥有個靜態鏈,還有個動態鏈。靜態鏈就是我們剛才說的,用于進行變量查詢。動態鏈就是之前棧式結構中的老SP,用來表示調用關系的。
??用一個例子,來說明下:
??想想靜態鏈有什么問題嗎?如果嵌套了特別多層,最內層想要調用最外層的變量需要一層一層向上查找。拿著必定會特別慢。有沒有什么方式可以加快速度呢?這就是Display表!
??Display表
??Display表的思想就是反正一層一層查找的目的就是為了引用外層變量,那我直接把所有的外層變量放在本層的活動記錄里不就可以嘛。這樣免得去其他層找。變量的個數不固定,導致表的空間大小不固定,所以也是動態分配的辦法,其活動記錄結構如下:
??舉個例子(當然使用標序號的方式,活動記錄里都是序號了哈~):
??上圖里面有一項叫做:全局display,這個指的是上一層的display段開始部分對應的標號。活動記錄中display段的個數就反映了當前的嵌套層數。第一層的全局display默認為0。
??總結一下靜態鏈和Display表的優缺點:
??靜態鏈優點:空間固定,且占用空間小 缺點:訪問速度太慢
??Display表優點:訪問速度快 缺點:占用空間比較大
堆式動態分配
??在定義中使用“適當大”來形容空間分配的大小,但實際上堆式分配的大小是由用戶指定。
??那如此多的空間,如何選出一個合適的呢?其實有三種策略:
??對于堆式分配,比較重要的一點就是內存空間的釋放問題。如果不釋放的話,內存就會被切分成小塊兒,導致以后使用的時候沒有足夠大的分配。這也告誡使用者在malloc之后加free,在new后加delete。
??內存的分配始終是計算機科學的一個大的組成部分,如今在編譯原理中又學了一遍,漲了不少見識。但還是冰山一角,日后多多加油!
總結
- 上一篇: IAR 窗口重置默认配置
- 下一篇: 《论文笔记》Experimental R