聲明:本系列文章,是根據中國大學MOOC網 哈工大的編譯原理 這門課學習而成的學習筆記。
運行存儲分配
一、運行存儲分配概述
運行存儲分配策略
- 編譯器在工作過程中,必須爲源程序中出現的一些數據對象分配運行時的存儲空間
- 對於那些在編譯時刻就可以確定大小的數據對象,可以在編譯時刻就爲它們分配存儲空間,這樣的分配策略稱爲靜態存儲分配
- 反之,如果不能在編譯時完全確定數據對象的大小,就要採用動態存儲分配的策略。即在編譯時僅產生各種必要的信息,而在運行時刻,再動態地分配數據對象的存儲空間
- 棧式存儲分配
- 堆式存儲分配
運行時內存的劃分
活動記錄
- 使用過程(或函數、方法)作爲用戶自定義動作的單元的 語言,其編譯器通常以過程爲單位分配存儲空間
- 過程體的每次執行稱爲該過程的一個活動(activation)
- 過程每執行一次,就爲它分配一塊連續存儲區,用來管理過程一次執行所需的信息,這塊連續存儲區稱爲活動記錄( activation record )
活動記錄的一般形式
二、靜態存儲分配
靜態存儲分配
- 在靜態存儲分配中,編譯器爲每個過程確定其活動記錄在目標程序中的位置
- 這樣,過程中每個名字的存儲位置就確定了
- 因此,這些名字的存儲地址可以被編譯到目標代碼中
- 過程每次執行時,它的名字都綁定到同樣的存儲單元
靜態存儲分配的限制條件
- 適合靜態存儲分配的語言必須滿足以下條件
- 數組上下界必須是常數
- 不允許過程的遞歸調用
- 不允許動態建立數據實體
- 滿足這些條件的語言有BASIC和FORTRAN等
常用的靜態存儲分配方法
- 順序分配法
- 層次分配法
順序分配法
- 按照過程出現的先後順序逐段分配存儲空間
- 各過程的活動記錄佔用互不相交的存儲空間
層次分配法
通過對過程間的調用關係進行分析,凡屬無相互調用關係的並列過程,儘量使其局部數據共享存儲空間
層次分配算法
三、棧式存儲分配
棧式存儲分配
- 有些語言使用過程、函數或方法作爲用戶自定義動作的單元,幾乎所有針對這些語言的編譯器都把它們的(至少一部分的)運行時刻存儲以棧的形式進行管理,稱爲棧式存儲分配
- 當一個過程被調用時,該過程的活動記錄被壓入棧;當過程結束時,該活動記錄被彈出棧
- 這種安排不僅允許活躍時段不交疊的多個過程調用之間共享空間,而且允許以如下方式爲一個過程編譯代碼:它的非局部變量的相對地址總是固定的,和過程調用序列無關
活動樹
- 用來描述程序運行期間控制進入和離開各個活動的情況的樹稱爲活動樹
- 樹中的每個結點對應於一個活動。根結點是啓動程序執行的main過程的活動
- 在表示過程p的某個活動的結點上,其子結點對應於被p的這次活動調用的各個過程的活動。按照這些活動被調用的順序,自左向右地顯示它們。一個子結點必須在其右兄弟結點的活動開始之前結束
最開始main進棧,r進棧
r調用完畢,人出棧,q(1,9)進棧,同時,q(1,9)調用p(1,9),故p(1,9)進棧
P(1,9)調用結束,出棧,同時q(1,9)繼續調用q(1,3),q(1,3)進棧,q(1,3)調用p(1,3),p(1,3)進棧。
設計活動記錄的一些原則
四、調用序列和返回序列
調用序列和返回序列
- 過程調用和過程返回都需要執行一些代碼來管理活動記錄棧,保存或恢復機器狀態等
- 調用序列
實現過程調用的代碼段。爲一個活動記錄在棧中分配空間,並在此記錄的字段中填寫信息 - 返回序列
恢復機器狀態,使得調用過程能夠在調用結束之後繼續執行
- 調用序列
- 一個調用代碼序列中的代碼通常被分割到調用過程(調用者)和被調用過程(被調用者)中。返回序列也是如此
調用序列
返回序列
調用者和被調用者之間的任務劃分
變長數據的存儲分配
- 在現代程序設計語言中,在編譯時刻不能確定大小的對象將被分配在堆區。但是,如果它們是過程的局部對象,也可以將它們分配在運行時刻棧中。儘量將對象放置在棧區的原因:可以避免對它們的空間進行垃圾回收,也就減少了相應的開銷
- 只有一個數據對象局部於某個過程,且當此過程結束時它變得不可訪問,纔可以使用棧爲這個對象分配空間
訪問動態分配的數組
五、非局部數據的訪問
非局部數據的訪問
一個過程除了可以使用過程自身定義的局部數據以外,還可以使用過程外定義的非局部數據
語言可以分爲兩種類型
- 支持過程嵌套聲明的語言
可以在一個過程中聲明另一個過程 ,例: Pascal - 不支持過程嵌套聲明的語言
不可以在一個過程中聲明另一個過程,例:C
支持過程嵌套聲明的語言
不支持過程嵌套聲明的語言
無過程嵌套聲明時的數據訪問
變量的存儲分配和訪問
- 全局變量被分配在靜態區,使用靜態確定的地址訪問它們
- 其它變量一定是棧頂活動的局部變量。可以通過運行時刻棧的top_sp指針訪問它們
有過程嵌套聲明時的數據訪問
嵌套深度
- 過程的嵌套深度
不內嵌在任何其它過程中的過程,設其嵌套深度爲1
如果一個過程p在一個嵌套深度爲i的過程中定義,則設定p 的嵌套深度爲i +1 - 變量的嵌套深度
將變量聲明所在過程的嵌套深度作爲該變量的嵌套深度
訪問鏈 (Access Links)
- 靜態作用域規則:只要過程b的聲明嵌套在過程a的聲明中,過程b就可以訪問過程a中聲明的對象
- 可以在相互嵌套的過程的活動記錄之間建立一種稱爲**訪問鏈(Access link)**的指針,使得內嵌的過程可以訪問外層過程中聲明的對象
- 如果過程b在源代碼中直接嵌套在過程a中(b的嵌套深度比a的嵌套深度多1),那麼b的任何活動中的訪問鏈都指向最近的a的活動
訪問鏈的建立
六、堆式存儲分配
堆式存儲分配
- 堆式存儲分配是把連續存儲區域分成塊,當活動記錄或其它對象需要時就分配
- 塊的釋放可以按任意次序進行,所以經過一段時間後,對可能包含交錯的正在使用和已經釋放的區域
堆式存儲分配的示意圖
申請
設當前自由塊總長爲M,欲申請長度爲n
如果存在若干個長度大於或等於n的存儲塊,可按以下策略之一進行存儲分配
- 取長度m滿足需求的第1個自由塊,將長度爲m-n的剩餘部 分仍放在自由鏈中
- 取長度m滿足需求的最小的自由塊
- 取長度m滿足需求的最大的自由塊
如果不存在長度大於或等於n的存儲塊
- 如果M≥n,將自由塊在堆中進行移位和重組(對各有關部分都需作相應的修改,是一件十分複雜和困難的工作)
- 如果M<n,則應採用更復雜的策略來解決堆的管理問題
釋放
只需將被釋放的存儲塊作爲新的自由塊插入自由鏈中,並刪除已佔塊記錄表中相應的記錄即可
爲實現堆式存儲管理
- 須完成大量的輔助操作
如排序、查表、填表、插入、刪除、…… - 其空間和時間的開銷較大
七、符號表
符號表的組織
爲每個作用域(程序塊)建立一個獨立的符號表