北航程序設(shè)計(jì)語(yǔ)言原理教材第04節(jié)-note_第1頁(yè)
北航程序設(shè)計(jì)語(yǔ)言原理教材第04節(jié)-note_第2頁(yè)
北航程序設(shè)計(jì)語(yǔ)言原理教材第04節(jié)-note_第3頁(yè)
北航程序設(shè)計(jì)語(yǔ)言原理教材第04節(jié)-note_第4頁(yè)
北航程序設(shè)計(jì)語(yǔ)言原理教材第04節(jié)-note_第5頁(yè)
免費(fèi)預(yù)覽已結(jié)束,剩余26頁(yè)可下載查看

下載本文檔

版權(quán)說(shuō)明:本文檔由用戶(hù)提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡(jiǎn)介

1、第4章第4章存儲(chǔ)如前所述,程序世界、機(jī)器世界是同一事物的不同層次描述。在每個(gè)層次上各有自己的術(shù) 語(yǔ)概念。我們的目標(biāo)雖然是為程序世界提供表達(dá)程序的術(shù)語(yǔ)和語(yǔ)言,因?yàn)樗鼈兠芮邢嚓P(guān)。我們 也應(yīng)該了解機(jī)器世界里存儲(chǔ)對(duì)象有哪些,怎樣工作,可以實(shí)現(xiàn)哪些機(jī)制。這些工作機(jī)制反映到 程序世界是什么?在某種意義上說(shuō),越能在上層構(gòu)造出更新、更多的符合軟件工程原理的程序 對(duì)象,越要在下層想出更多的實(shí)現(xiàn)辦法,如果都能實(shí)現(xiàn),則程序設(shè)計(jì)語(yǔ)言就進(jìn)了一步。有關(guān)存儲(chǔ)對(duì)象的基本術(shù)語(yǔ)和機(jī)制,我們假定讀者已在匯編語(yǔ)言課程學(xué)過(guò),此處不重復(fù), 只是在涉及到機(jī)器世界術(shù)語(yǔ)和機(jī)制時(shí)我們才提到它。請(qǐng)注意,我們討論問(wèn)題的立場(chǎng)是程序世 界。還要注意到,

2、程序?qū)ο蠛痛鎯?chǔ)對(duì)象并非一一對(duì)應(yīng)的,賦初值的簡(jiǎn)單常數(shù)一般也是不作為獨(dú) 立的存儲(chǔ)對(duì)象,直接放在被賦值的變量中。所以研究存儲(chǔ)對(duì)象的核心是抓住“變量”。本章主要討論變量的時(shí)空特性,第二節(jié)介紹各種實(shí)現(xiàn)計(jì)算的存貯模型,第三節(jié)討論因時(shí)空 關(guān)系而出現(xiàn)的懸掛指升問(wèn)題,第四節(jié)討論各種語(yǔ)言變量更新機(jī)制,第五節(jié)介紹有副作用的表達(dá) 式。4.1程序變量的時(shí)、空特性如前所述,程序世界中的變量不同于數(shù)學(xué)變量在于它的時(shí)、空特性。我們先從空間特性開(kāi) 始。我們知道,計(jì)算機(jī)內(nèi)的變量的存儲(chǔ)空間是隨時(shí)可變的,它可以在外存磁盤(pán)上,也可以在內(nèi) 存的某個(gè)地方,而且同一程序兩次加載運(yùn)行變量也不會(huì)在一個(gè)地方,即非固定地址。為此, 一般目標(biāo)程序中都

3、采用相對(duì)地址的辦法,絕對(duì)地址就不重要了。對(duì)于程序,我們要找到某個(gè)計(jì) 算對(duì)象,永遠(yuǎn)從該段程序的首地址開(kāi)始。為了簡(jiǎn)化,我們?cè)谔摯婵臻g討論存儲(chǔ)對(duì)象。'取地址4.1.1 引用和指針由于變量的名值分離,當(dāng)程序操縱變量時(shí),首先尋址再取內(nèi)容。這是兩種操作'(引用)和取值'(遞引用)。命令式語(yǔ)言?xún)烧叨家矣袝r(shí)是隱式的。自從Pascal,C引入指針變量可顯式操縱變量的地址,給程序員帶來(lái)極大方便。例如,一個(gè)大單位,隨機(jī)錄用上千個(gè)員 工。每個(gè)員工履歷是一復(fù)雜記錄,現(xiàn)登記現(xiàn)錄入無(wú)序存放。我們只要按它的某個(gè)屬性特征對(duì)指 向它們的首地址(指針)排序,即可按排好序的指針表次序打印各種報(bào)告,而無(wú)需將

4、龐大的記錄 重排多次。指針本身也是程序?qū)ο?,它也有地址,于是C語(yǔ)言允許多級(jí)指針的機(jī)制。其它語(yǔ)言編譯只允許一級(jí)指針,除非指向復(fù)雜數(shù)據(jù)結(jié)構(gòu)內(nèi)嵌的指針。用f(Pascal)和*(C)標(biāo)記指針變量標(biāo)識(shí)符以識(shí)別是操縱指針變量的內(nèi)容(地址),還是操縱指針變量所指對(duì)象的內(nèi)容(值)。例4-1 , C程序中的指針int i;第94頁(yè)P(yáng)P P q g q&P*p+1 p+1/P/q/'1'指向整變量i錯(cuò)誤,p中只放地址值正確,正確,指向i,效果同*p=i , &取地址符 同類(lèi)型賦值,q, P都指向i此時(shí)i已成為i+1僅是名,值取決于所指對(duì)象每個(gè)單元占多少字節(jié)。q;int * p所謂

5、尋址即要給出編譯(或解釋)時(shí)程序變量名與地址的對(duì)照表。每當(dāng)給程序?qū)ο蠓峙浯?儲(chǔ)時(shí)就在表中填入,這樣就創(chuàng)建了引用(refere nee)。由于程序?qū)ο竺志幾g后不存在,每次4-1 O操縱用的是地址,這個(gè)在對(duì)照表中有的地址即為該對(duì)象的別名。早期語(yǔ)言認(rèn)為這是屬于內(nèi) 部的事,外叫變量,內(nèi)叫地址碼。而C+語(yǔ)言把這個(gè)占據(jù)存儲(chǔ)的存儲(chǔ)對(duì)象也上升為程序?qū)ο螅凶鲆?,使用?hù)直接操縱地址。引用,指針、變量關(guān)系的示意圖如圖P1p22(RA)3113631140 32O(RC)144500488504450(P1)4481361364274.54(A)引用(P2)140 I1443312.27607.01(B)( C

6、)指針圖4-1變量、指針、引用示意圖變量A的地址碼分配為136放在312中,每當(dāng)程序中用 A的左值,查出136即是,若用A的右 值查到136再找136的內(nèi)容。既然312也是存儲(chǔ)單元地址,則把它對(duì)應(yīng)為一引用名RA只是一旦給出136再也不許改變,它成了 A的別名,相當(dāng)于常量指針。指針P1, P2既是程序?qū)ο?,它們被分配?48,450存儲(chǔ)單元內(nèi),地址碼 448,450同樣成為P1, P2的代替物。如果在 448中給予 136地址則P1指向A。給140則指向B。其內(nèi)容是可變的,所以和引用RA不一樣。RA雖然也放一個(gè)地址,但更強(qiáng)調(diào)的是引用內(nèi)容。即當(dāng)RA A同時(shí)出現(xiàn)在賦值號(hào)右邊,其效果完全一樣,這一點(diǎn)和

7、常量指針又不一樣。它和變量A不同之處僅在實(shí)現(xiàn)上,效果是相同的,而且引用變量必須賦初值,一旦賦初值則在其作用域在其所在程序單元的范圍內(nèi)不得改動(dòng)。C+ 和C引入引用主要是用于函數(shù)調(diào)用的傳地址。形、實(shí)結(jié)合正好相當(dāng)于引用變量賦初 值。(這在第6章中還要講)。4.1.2 遞引用般說(shuō)來(lái),通過(guò)指針變量引用某變量的內(nèi)容叫做遞引用(derefere nee)。多層指針則多次遞引用。指針用或 * '顯式地表達(dá)了它的遞引用。但多數(shù)程序設(shè)計(jì)語(yǔ)言都有隱式的 遞引用。例如,下標(biāo)表達(dá)式,除了引用下標(biāo)變量值再引用該元素的值,即使出現(xiàn)在賦值號(hào)的左 邊的下標(biāo)表達(dá)式也是遞引用。我們稱(chēng)這是自動(dòng)遞引用。FORT語(yǔ)言是唯一不容許

8、自動(dòng)遞引用的,它只有顯式遞引用運(yùn)算符例4-2 FORT H的遞引用運(yùn)算符1 13 VARIABLE xx2 0 VARIABLE Y3 XX 2 * Y !如果只寫(xiě)XX 2 *聲明變量XX并賦初值13) 聲明變量丫并賦初值0)相當(dāng)于Y=xx*2)(則為將XX的地址乘以2放在Y之中。4.1.3 變量的時(shí)態(tài)除了空間名值分離導(dǎo)出指針、引用、遞引用程序世界的概念之外,變量還有時(shí)間特性, 以它的狀態(tài)刻劃:(1) .分配/未分配/除分配為程序?qū)ο蠓峙浯鎯?chǔ)就創(chuàng)建了存儲(chǔ)對(duì)象,程序?qū)ο缶涂梢栽L(fǎng)問(wèn)了。如果在編譯時(shí)做這個(gè)工作我們叫靜態(tài)分配,程序中變量多數(shù)如此。如果在程序運(yùn)行時(shí)刻分配存儲(chǔ),我們叫動(dòng)態(tài)分 配,程序中的指

9、針(或訪(fǎng)問(wèn))類(lèi)型變量則按動(dòng)態(tài)分配,一般由程序員顯式指定(用new操作)。若程序?qū)ο舐暶髁说捶峙浯鎯?chǔ)對(duì)象即投入運(yùn)行,此程序?qū)ο筇幱谖捶峙錉顟B(tài)。撤消程序?qū)ο蠹闯ヒ逊峙涞拇鎯?chǔ)對(duì)象,我們叫除分配或除配(deallocate)。除分配可由程序員顯式指定(用delete操作),也可以約定由語(yǔ)言的執(zhí)行系統(tǒng)或操作系統(tǒng)自動(dòng)實(shí)現(xiàn)。自動(dòng)將當(dāng)前無(wú)用的程序 對(duì)象除配,并收回待用稱(chēng)無(wú)用單元回收(Garbage collectio n)機(jī)制。動(dòng)態(tài)(或無(wú))類(lèi)型語(yǔ)言一般有此機(jī)制,靜態(tài)類(lèi)型語(yǔ)言雖然也有動(dòng)態(tài)分配部分,往往不設(shè)此機(jī)制(如C語(yǔ)言)(2) .定義/未定義/失去定義分配的存儲(chǔ)單元總是有殘值的(上個(gè)程序運(yùn)行后留下的),這

10、些值無(wú)任何意義(與本程序無(wú) 定義變量,除了它的值對(duì)本程序計(jì)(例如,一個(gè)局部塊的末端), 以下舉例說(shuō)明。關(guān))。我們說(shuō),變量 未定義。如果通過(guò)賦值或賦初值,就是 算是有意義的。如果該變量執(zhí)行超出了我們指定它有意義的區(qū)域 只要它的存儲(chǔ)沒(méi)有撤消,它總是存在,但其內(nèi)容(值)已失去定義。例4-3 變量的狀態(tài)(Ada)P rocedure MAIN is declare BLOCK ;type CELL ;type LINK is access CELL;type CELL is recordVALUE:INTEGER;NEXT: LINK;end record;type VECTOR is array (

11、INTEGER range< >) of FLOAT;LA:LINK ; V: VECTOR ;VB:VECTOR(1.5)beginLA:=new CELLLA.all:=(3120聲明訪(fǎng)問(wèn)類(lèi)型變量 聲明數(shù)組類(lèi)型變量 聲明數(shù)組類(lèi)型變量LA,但未分配V 未分配VB,運(yùn)行前已分配但未定義,n ull)動(dòng)態(tài)分配無(wú)名CELL對(duì)象由LA代名 定義LA所訪(fǎng)問(wèn)對(duì)象V:=(1=>5.04=>2.0,2=>4.0 ;3=>3.0,5=>1.0 , 6=>0.0)-分配V為六元素?cái)?shù)組,且得到定義,動(dòng)態(tài)完成 只有VB(3)得到定義,VB的其余元素未定義VB(3) e

12、nd BLOCK L : = LA ;:=V(1);-LA-V-Ada值已失去定義,出錯(cuò),VB(3)的值均失去定義有無(wú)用單元收集,此處可將失去定義的變量存儲(chǔ)回收end MAIN;4.1.4可存儲(chǔ)值就變量訪(fǎng)問(wèn)和更新而言,我們把可以直接訪(fǎng)問(wèn)和更新的存儲(chǔ)對(duì)象的值稱(chēng)為可存儲(chǔ)值 (storable)。它們是可訪(fǎng)問(wèn)的最小存儲(chǔ)單元。因此,復(fù)合值如果它們的成分不能選擇更新, 該復(fù)合值即為可存儲(chǔ)值。例如,Pascal的集合值,不能單獨(dú)更新其中某個(gè)元素,它是可存儲(chǔ)值。反之,數(shù)組元素是可以選擇更新的。整個(gè)的記錄、文件也都不是可存儲(chǔ)值。所以, Pascal中可存儲(chǔ)值是:基本類(lèi)型值C+語(yǔ)言設(shè)置了引用集合值 指針值 變量

13、引用、函數(shù)過(guò)程抽象也不是可存儲(chǔ)值,它們本身不是可存儲(chǔ)的。 類(lèi)型變量,變量引用有了名字,它是可存儲(chǔ)的。ML的可存儲(chǔ)值有:基本類(lèi)型值 記錄、構(gòu)造、表 函數(shù)抽象 變量引用ML的記錄、構(gòu)造、表是可存儲(chǔ)值,因?yàn)樗鼈儾荒苓x擇更新,更新其中一個(gè)元素存儲(chǔ)要重整一遍。由于函數(shù)抽象和變量引用全部采用類(lèi)似C+引用類(lèi)型的辦法,借助于引用可以有選擇地更新復(fù)合值。事實(shí)上除數(shù)組而外,ML所有的值均為可存儲(chǔ)值。例4-4 ML的函數(shù)抽象是可存儲(chǔ)值函數(shù)名即函數(shù)抽象,在 Pascal中,f(x)的f是不能單獨(dú)存取和更新的,ML中卻可寫(xiě)為:(if exp the n sin else cos) (x)這個(gè)條件表達(dá)式的返回值(sin或

14、cos)是函數(shù)抽象,sin(x)是函數(shù)定義,也叫型構(gòu) (signature), sin(a)是函數(shù)引用。后二者都不是可存儲(chǔ)值。4.2組織存儲(chǔ)對(duì)象的存儲(chǔ)模型存儲(chǔ)管理模型是翻譯器必須選定的關(guān)鍵部分,各語(yǔ)言不盡相同,但從總體說(shuō)有三種存儲(chǔ) 模型,靜態(tài)存儲(chǔ),堆存儲(chǔ),棧存儲(chǔ)。后二者也稱(chēng)動(dòng)態(tài)存儲(chǔ)模型。421存儲(chǔ)對(duì)象的生命期每個(gè)存儲(chǔ)對(duì)象都有其創(chuàng)建 (生)、可用(活著)、撤消(死)的生命期。每當(dāng)分配存儲(chǔ)即創(chuàng)建 存儲(chǔ)對(duì)象。當(dāng)所在程序塊或整個(gè)程序執(zhí)行結(jié)束,程序?qū)ο笏劳?,存?chǔ)對(duì)象也應(yīng)自動(dòng)撤銷(xiāo)。但多 數(shù)對(duì)象生命期不會(huì)像程序執(zhí)行期一樣長(zhǎng),也有少量對(duì)象的生命期比程序還要長(zhǎng)。我們把壽命和 程序一樣長(zhǎng)的變量稱(chēng)為全局變量;壽命和

15、程序中一個(gè)或幾個(gè)模塊一樣長(zhǎng)的變量稱(chēng)局部變量;壽命大于程序執(zhí)行期的變量稱(chēng)持久變量。文件變量即持久變量。與此對(duì)應(yīng),程序中變量都叫臨時(shí)變量(包括全局、局部、靜態(tài)、堆變量活著的對(duì)象必須在內(nèi)存中才可用。然而,由于有虛擬存儲(chǔ)技術(shù)。外存上虛存中的對(duì)象也是 活對(duì)象,除非所占存儲(chǔ)被撤銷(xiāo)并分配給其它程序?qū)ο蟆T诔绦驁?zhí)行期間,一個(gè)存儲(chǔ)單元可多次分配給局部變量,甚至同一存儲(chǔ)對(duì)象可由多個(gè)程序 對(duì)象共享,如C的聯(lián)合,Pascal、Ada的變體記錄。FORTRAN價(jià)語(yǔ)句指明的各變量。反過(guò)來(lái),一個(gè)程序?qū)ο罂捎啥鄠€(gè)存儲(chǔ)對(duì)象實(shí)現(xiàn)。如前述的引用、指針。程序?qū)ο笏懒?,?duì)應(yīng)的存儲(chǔ)對(duì)象沒(méi)撤銷(xiāo),此時(shí)引用該對(duì)象就會(huì)引起難以預(yù)計(jì)的錯(cuò)誤。分 配

16、了沒(méi)有賦(初)值就引用也會(huì)引起錯(cuò)誤。這兩種錯(cuò)誤在程序調(diào)試中屢見(jiàn)不鮮。422靜態(tài)存儲(chǔ)對(duì)象在程序運(yùn)行之前分配的程序?qū)ο蠖冀徐o態(tài)存儲(chǔ)對(duì)象。編譯時(shí)分配自不用說(shuō)。近代語(yǔ)言有 在裝入內(nèi)存后,運(yùn)行前分配,Ada稱(chēng)之為確立(elaboration)期間(確立除分配某些對(duì)象外還計(jì)算初值表達(dá)式定義初值)。之所以稱(chēng)靜態(tài),因它們?cè)诜峙渲笳麄€(gè)程序執(zhí)行期間不再改動(dòng)。靜 態(tài)分配常伴之以初始化。所有語(yǔ)言的全局變量都是靜態(tài)對(duì)象。某些語(yǔ)言(COBOL)只有靜態(tài)對(duì)象;某些語(yǔ)言(P ascal)除全局變量而外沒(méi)有靜態(tài)對(duì)象;而C和Algol允許程序員把局部變量聲明為靜態(tài)的,即該局部塊執(zhí)行結(jié)束靜態(tài)變量并不失去定義,該塊下次還可用,但同

17、一程序其它局部塊卻不能用。所以 Algol把它叫做OWN自己的),聲明中顯式指明。C則用Static ,C的extern變量全局于所在文件也是靜態(tài)的。靜態(tài)存儲(chǔ)對(duì)象直觀(guān)易于查錯(cuò),但它不能支持遞歸,因?yàn)槎啻芜f歸調(diào)用相關(guān)的動(dòng)態(tài)參數(shù)無(wú) 法分配(不知道遞歸幾次)。靜態(tài)存儲(chǔ)對(duì)象僅限于全局變量的第二個(gè)缺點(diǎn)是被迫使用易引起副作用的全局量。對(duì)于某 些問(wèn)題(如求隨機(jī)數(shù),輸入/出程序指示當(dāng)前輸入/出位置的指針)需要在程序多次執(zhí)行時(shí)值有 連續(xù)性,因而沒(méi)有執(zhí)行后仍保持值的變量(若完全是局部變量執(zhí)行后自動(dòng)銷(xiāo)毀)無(wú)法模型客觀(guān)世界。然而,采用全局變量其它無(wú)關(guān)模塊也可使用就會(huì)引起副作用,C的靜態(tài)變量即為局限于一個(gè)文件(模塊)的

18、局部變量,即保持值的連續(xù)性又有保護(hù)作用。靜態(tài)存儲(chǔ)對(duì)象,無(wú)論是全局量還是局部量的第三個(gè)缺點(diǎn)(也是它的優(yōu)點(diǎn))是占據(jù)的存儲(chǔ)不FORTRA的等價(jià)語(yǔ)句(新一代語(yǔ)言的聯(lián)合、變體記錄 )C語(yǔ)言叫自動(dòng)變量)是靜態(tài)程序中的局部變量(到程序執(zhí)行完不釋放。對(duì)于大程序且有眾多只需短壽命變量的應(yīng)用就浪費(fèi)了大量存儲(chǔ)。即使有 無(wú)用單元回收機(jī)制不到執(zhí)行完也收不到它, 就為(起到)緩解作用而設(shè)。請(qǐng)注意,靜態(tài)分配的不一定是靜態(tài)變量, (編譯時(shí))分配的,運(yùn)行時(shí)存貯的地址可變。423 動(dòng)態(tài)存儲(chǔ)對(duì)象動(dòng)態(tài)存儲(chǔ)對(duì)象在程序執(zhí)行期間誕生(分配和定義),故名動(dòng)態(tài)。多數(shù)動(dòng)態(tài)對(duì)象它們的大小和結(jié)構(gòu)形態(tài)往往依賴(lài)輸入,編譯時(shí)無(wú)法確定。例如,遞歸定義的二叉

19、樹(shù),其結(jié)點(diǎn)、葉子、層數(shù) 完全由輸入值定。因此,動(dòng)態(tài)存儲(chǔ)對(duì)象生命期長(zhǎng)短不同,大小不同,類(lèi)型各異,如何使存儲(chǔ)管 理高效是一個(gè)中心問(wèn)題。管理不好對(duì)程序執(zhí)行的時(shí)、空效率影響極大。在某種意義上說(shuō),它是 發(fā)展先進(jìn)語(yǔ)言或軟件技術(shù)的關(guān)鍵。例如,Prolog、Smalltalk出現(xiàn)初期都是執(zhí)行效率難以和傳統(tǒng)語(yǔ)言匹敵的,慢 5-30倍。不斷改進(jìn)才能進(jìn)入實(shí)用。影響最大的因素是動(dòng)態(tài)存儲(chǔ)對(duì)象的生命 期,而不同生命期存儲(chǔ)對(duì)象搭配使用,恰巧是增加表達(dá)能力,減少程序錯(cuò)誤的進(jìn)步。所以一般 語(yǔ)言提供管理不同的生命期對(duì)象的模型。最簡(jiǎn)單的模型是在內(nèi)存中開(kāi)辟一個(gè)堆(heaP)空間,放入其中的動(dòng)態(tài)對(duì)象的壽命完全由程序員控制,語(yǔ)言系統(tǒng)全然

20、不知。每當(dāng)創(chuàng)建一新存儲(chǔ)對(duì)象,存儲(chǔ)管理則找出一個(gè)足以放下該對(duì)象的堆空間。每當(dāng)存儲(chǔ)管理得知(出了所在程序塊)它已死亡,它就記下該對(duì)象不再使用。隨來(lái)隨堆,誰(shuí)死誰(shuí)釋放,新來(lái)的沒(méi)地方就等到老的死了再存入,故名堆。一般的指針動(dòng)態(tài)生成的對(duì)象即按此型式,故指針變量也叫堆變量。管理堆型式最大的問(wèn)題是多次存儲(chǔ)后留下存儲(chǔ)碎片,因?yàn)樾聦?duì)象的大小往往不能填足老 對(duì)象的空間,時(shí)間長(zhǎng)了形成星星點(diǎn)點(diǎn)的許多“小洞”,管理很麻煩。拿出記錄該碎片信息所費(fèi) 存儲(chǔ)比這些“小洞”本身小不了多少,而且運(yùn)行時(shí)一個(gè)到一個(gè)鏈接十分低效。再者還沒(méi)有能找 出合適的識(shí)別算法將“小洞”合并。這些問(wèn)題各語(yǔ)言系統(tǒng)都采取了各自的做法。本節(jié)將介紹主 要做法。由

21、于堆型式管理存在著麻煩,人們尋求并得到第二種管理型式:嵌套生命期型式,按照 這種型式任何兩同時(shí)出現(xiàn)的對(duì)象其生命期長(zhǎng)短可以不同,但必須完全嵌套,即不能交錯(cuò)。也就 是在時(shí)間上一個(gè)包容一個(gè),我們就可以把相近壽命變量歸成一組,最長(zhǎng)壽命組放在堆棧的底部最短的在頂部,逐級(jí)釋放和重新占據(jù)存儲(chǔ),可以得到成片干凈的存儲(chǔ)。如圖4-2所示。塊11 塊2 塊3塊4 I 塊51塊6 IJ4r6-r-l' :XXI,14J亠丄"Lbbbxbbbbxi圖4-2嵌套生命期存儲(chǔ)原理圖6組不同壽命的存儲(chǔ)塊。由于塊6中對(duì)象壽命最短,全部重新分配3次本例依次分配了塊4,塊5可重新分配兩次而塊 1的一次生命期還沒(méi)完。

22、如同倒下來(lái)的堆棧,棧頂?shù)膶?duì)象生得 快死得也快,死了清除再分配第二批。而棧底的對(duì)象一直活著。事實(shí)上,動(dòng)態(tài)對(duì)象壽命長(zhǎng)短本 來(lái)就和所在嵌套模塊相關(guān),只要把壽命特長(zhǎng)的(如文件,全局量)排出來(lái)歸到棧底的某一組,把壽命特短的(如循環(huán)控制變量)另立嵌套組,這個(gè)分組的問(wèn)題也就解決了。424動(dòng)態(tài)堆棧存儲(chǔ)按照嵌套生命期存貯原理,對(duì)于嵌套塊結(jié)構(gòu)的程序設(shè)計(jì)語(yǔ)言如(Pascal,Ada, C)動(dòng)態(tài)堆棧型管理特別有效,因?yàn)槎鄶?shù)變量和所在塊壽命一樣長(zhǎng)。變量生死隨所在塊的激活和消亡高效 自動(dòng)實(shí)現(xiàn)。所以C語(yǔ)言把局部于塊結(jié)構(gòu)的變量叫做auto(自動(dòng))變量。但多數(shù)語(yǔ)言(包括C)堆、堆棧管理同時(shí)并用(原因見(jiàn)后文)。4.2.4.1動(dòng)態(tài)

23、堆棧式管理機(jī)制為了說(shuō)明堆棧式管理,我們?cè)賲⒄請(qǐng)D 如圖4-3所示。4-2運(yùn)行時(shí)堆棧,結(jié)合執(zhí)行過(guò)程,介紹其實(shí)現(xiàn)機(jī)制,f參數(shù) XkLBBJXLB-S-h返回地址 =動(dòng)態(tài)鏈 靜態(tài)鏈“返回值"”“局部變量程序代碼,全局靜態(tài)存儲(chǔ) 首先調(diào)用塊:堆棧幀最新調(diào)用塊堆棧幀棧頂:第二調(diào)用塊 堆棧幀臨時(shí)變量空間運(yùn)行時(shí)堆棧堆棧幀組織圖4-3 運(yùn)行時(shí)堆棧和堆棧框架實(shí)現(xiàn)運(yùn)行時(shí)堆棧(Run-time stack)是在內(nèi)存開(kāi)辟一個(gè)空間,如同堆。首先將程序代碼和全局、靜態(tài)變量裝入,示意為圖4-3的右圖最上一塊。這一塊的詞法上輩(Lexical Parent) 是操作系統(tǒng)。當(dāng)執(zhí)行控制開(kāi)始時(shí),首先執(zhí)行這一塊,當(dāng)執(zhí)行到調(diào)用時(shí)

24、,按編譯時(shí)生成的嵌套關(guān)系, 找出被調(diào)用的塊,將該塊的數(shù)據(jù)(包括實(shí)參和局部量)作為新的一幀,記下靜態(tài)鏈(連接詞法祖先(Lexical ancestor),壓入運(yùn)行時(shí)堆棧后記下動(dòng)態(tài)鏈(連接執(zhí)行順序)。由于編譯時(shí)詞法祖先塊的地址無(wú)法確定,靜態(tài)鏈要根據(jù)祖先塊分配存儲(chǔ)對(duì)象去找,有時(shí)要用到局部變量和參 數(shù)。動(dòng)態(tài)鏈指出當(dāng)前塊的動(dòng)態(tài)執(zhí)行的上輩,以便在塊出口時(shí)彈出棧內(nèi)的執(zhí)行指令。動(dòng)態(tài)和靜態(tài) 鏈在壓入棧時(shí)利用局部分配指針從棧中第一個(gè)自由位置逐個(gè)向上(下)分配,記下該指針的值,以便以后彈棧。每個(gè)堆棧幀(stack frame) 的組織如圖4-3的左圖,它按以下事件序列壓棧:1 入,234 的。調(diào)用程序?qū)?shí)參值置于新

25、塊底部,用局部分配的指針,一般說(shuō),最后一個(gè)實(shí)參先放倒數(shù)第二個(gè)第二第一個(gè)放在最上面。接著放入新塊的返回地址。把當(dāng)前棧頂指針值拷貝到棧內(nèi),這是新塊動(dòng)態(tài)鏈的一個(gè)域(存放當(dāng)前棧頂?shù)刂罚?。填?xiě)靜態(tài)鏈,將編譯時(shí)生成的標(biāo)記碼拷貝進(jìn)來(lái),對(duì)于調(diào)用塊靜、動(dòng)態(tài)鏈標(biāo)記是一樣再留足夠的位置以放下局部變量的返回值,如已有初始化則拿來(lái)就可以了??刂妻D(zhuǎn)回父塊代碼執(zhí)行,直至到生成另一個(gè)內(nèi)嵌塊的堆棧幀,如此,運(yùn)行堆棧又生成一層如果某塊對(duì)應(yīng)的程序代碼出口則根據(jù)動(dòng)態(tài)鏈返回父塊,再生成復(fù)蓋的新的一層堆棧幀 老塊被復(fù)蓋也就是除分配了。此處為了簡(jiǎn)化,返回值假定在塊內(nèi),實(shí)際上常用寄存器。4.2.4.2用堆棧式存儲(chǔ)實(shí)現(xiàn)遞歸顯然,遞歸調(diào)用是堆棧

26、存儲(chǔ)管理最簡(jiǎn)單的形式。因?yàn)樗倪\(yùn)行棧每次壓入的是同一模塊 新的數(shù)據(jù)對(duì)象版本,靜態(tài)鏈自然按逐層遞歸調(diào)用展開(kāi)直至到達(dá)邊界,現(xiàn)舉例說(shuō)明。例4-4 P ascal的遞歸函數(shù)In teger): In teger ;求整數(shù)之連乘積function P roduct (jj:var kk : In teger;beginif jj <= 0 the n p roduct:=1else begi nreadl n (kk);p roduct:=kk * p roduct(jj-l)讀二次 kk=25 , 7 。endend ;其執(zhí)行圖示于圖4-4.為簡(jiǎn)化令jj=2 ,動(dòng)態(tài)鏈靜態(tài)鏈最初調(diào)用時(shí)堆棧幀第一次

27、調(diào)用時(shí)堆棧幀第一次調(diào)用時(shí)堆棧幀棧頂圖4-4遞歸函數(shù)的堆棧幀4.2.5 動(dòng)態(tài)堆存儲(chǔ)動(dòng)態(tài)堆棧存儲(chǔ)雖然解決了碎片問(wèn)題,但是存儲(chǔ)對(duì)象壽命只能和堆棧幀同時(shí)生死,限制太 嚴(yán)。而且在每一個(gè)塊的進(jìn)口處并不知道存儲(chǔ)對(duì)象的大小和個(gè)數(shù),以及要求存儲(chǔ)對(duì)象比創(chuàng)建它的塊的壽命長(zhǎng)。所以堆存儲(chǔ)有時(shí)是必不可少的。我們?cè)僭敿?xì)討論堆存儲(chǔ)。4.2.5.1 堆分配許多語(yǔ)言允許程序員顯式分配動(dòng)態(tài)存儲(chǔ),分配語(yǔ)句可以出現(xiàn)在程序中的任何地方,辦法 是調(diào)用操作系統(tǒng)ALLOC函數(shù)。ALLO(將存儲(chǔ)對(duì)象分配在堆中并返回對(duì)該對(duì)象的引用。如前所述堆 分配有時(shí)在連接的較小的空間上,算法比較復(fù)雜。所以要建立一處自由表登錄已死存儲(chǔ)對(duì)象的 空間,并查清相鄰的碎

28、片予以合并。這些算法還必須高效,否則影響性能。故往往是折衷,求快速犧牲一些過(guò)小碎片(這又是潛在危險(xiǎn),當(dāng)指針錯(cuò)誤地指向這些無(wú)用小碎片時(shí),后果極難預(yù)料)。各個(gè)語(yǔ)言組織動(dòng)態(tài)堆及分配后的返回值也不完全一樣。現(xiàn)例舉說(shuō)明:(1) FORTH的堆分配/除配 分配:HERE <表達(dá)式>ALLOT程序員通過(guò) HEREN, ALLOT各N個(gè)字FORTH在存放符號(hào)表和全局對(duì)象的字典中作動(dòng)態(tài)分配。HER是系統(tǒng)變量,另設(shè)一小棧記下新增區(qū)域存入,用戶(hù)只存入指針變量。 無(wú)無(wú)用單元回收。重新分配由用戶(hù)自己寫(xiě)例程。 的分配/除配(cons<表達(dá)式1><表達(dá)式2>)訪(fǎng)問(wèn)字典頂端指針。先把 HE

29、RE勺當(dāng)前值放入堆,然后對(duì)表達(dá)式求值得一整數(shù) 節(jié)加到字典指針。除配1的值填入,2式是N個(gè)字節(jié);第3(2) LIS P 分配:見(jiàn)到這個(gè)表達(dá)式就分配一新表,并返回對(duì)新表的引用。新表左域由表達(dá)式 右域用表達(dá)式2的值填入。除配:由無(wú)用單元收集機(jī)制自動(dòng)完成。(3) C的動(dòng)態(tài)分配/除配分配:malloc (sizeof(T) malloc(N) calloc(N, sizeof(baset yp e)式中T、basetype是類(lèi)型名,N是整數(shù)。第1式是分配T類(lèi)型的一個(gè)對(duì)象;第 式是分配一個(gè)數(shù)組,類(lèi)型為 basetype,元素是N個(gè),并初始化為0。malloc和calloc均返回對(duì) 新對(duì)象的引用。程序員必須

30、將返回值賦給某個(gè)指針類(lèi)型或強(qiáng)行轉(zhuǎn)成所希望的指針類(lèi)型。(再分配)。除配:free (ptr)ptr必須指向已分配過(guò)的堆對(duì)象。該對(duì)象經(jīng)此函數(shù)即可重用(4) Pascal動(dòng)態(tài)分配/除配分配:new (<指針名>)< 指針名 >必須是聲明為指向某類(lèi)型的指針。該指針存放引用返回值。除配:Dis pose (< 指針名>)< 指針名 >所指對(duì)象置入自由表。(5) Ada動(dòng)態(tài)分配/除配分配:new <TYPE>'(< 表達(dá)式 >)分配一 TYPE類(lèi)型的對(duì)象,如果提供了可選表達(dá)式,對(duì)它求值并用以初始化該新對(duì)象, new是函數(shù)返回對(duì)

31、新對(duì)象的引用。該返回值程序員應(yīng)賦給ACCES類(lèi)型變量(即指針)。除配:有無(wú)用單元回收,顯式除配一般不用,而是所在塊執(zhí)行完自動(dòng)完成。 425.2死堆對(duì)象處理死堆對(duì)象叫無(wú)用單元或垃圾(garbage)。堆對(duì)象死亡是當(dāng)它引用的對(duì)象已失去定義(即出了創(chuàng)建該對(duì)象的塊),我們稱(chēng)這種死亡是自然死亡。強(qiáng)行用 (或類(lèi)似)操作系統(tǒng)KILL命令也能顯 式使之死亡。自然死亡一般程序員和運(yùn)算支持系統(tǒng)都是察覺(jué)不到的,不知不覺(jué)在一個(gè)未知的(地址)置入自由表。在語(yǔ)言中實(shí)現(xiàn)KILL并拿出大量地方留下垃圾。死堆對(duì)象往往不在堆的末端而在中間,簡(jiǎn)單地KILL 命令在除配之后將該對(duì)象的引用 存儲(chǔ)以便追蹤,一直存在著很大的爭(zhēng)議?;厥账蓝?/p>

32、對(duì)象的存儲(chǔ)比堆存儲(chǔ)復(fù)雜得多。減少堆高指針是不行的。要把它們做成登錄可重分配存儲(chǔ)對(duì)象的自由表。效率高低全看自由表 的設(shè)計(jì),曾經(jīng)有過(guò)以下三種辦法:忽略死對(duì)象,這種表當(dāng)然最容易實(shí)現(xiàn)。這看上去近乎胡來(lái)的策略還真有實(shí)現(xiàn)的,如 AOD-V礫作系統(tǒng)上(數(shù)據(jù)通用公司的MV800(機(jī)的P ascal,在其參考手冊(cè)中明文寫(xiě)出 Dis pose命令 無(wú)其它操作。該操作系統(tǒng)是有虛存、頁(yè)式管理和分時(shí)的)。編譯實(shí)現(xiàn)者的哲學(xué)是:頁(yè)式管理若有浪費(fèi)充其量不到一頁(yè)。如果一頁(yè)上的對(duì)象全都死了 操作系統(tǒng)就不會(huì)把這頁(yè)裝入內(nèi)存。事實(shí)上,當(dāng)存儲(chǔ)對(duì)象的創(chuàng)建時(shí)間、壽命大致相差不多時(shí), 這種策略能工作得很好。否則浪費(fèi)巨大。保持一個(gè)自由表它將所有

33、自由空間連接在一起。這樣,每個(gè)自由空間都要有幾個(gè)字節(jié) 記錄它的大小并指示鏈接(多數(shù)硬件是8個(gè)字節(jié)),小于此數(shù)的空間也只能放棄了。多數(shù)C,Pascal編譯版本采用這個(gè)方案。8字節(jié)的頭部說(shuō)明是需要的:兩個(gè)指針以構(gòu)成雙向循環(huán)鏈表,一個(gè)字節(jié)指示該小塊是自由還是工作的。各小塊編排次序和內(nèi)存地址一致,這樣相鄰的小塊合 并就很方便了。死對(duì)象除配也很方便:只要把指示位設(shè)成“自由”。相鄰小塊都自由則合并。掃描到第一個(gè)自由區(qū)并接著找到足夠大的空間是很費(fèi)事的,因?yàn)槎鄶?shù)區(qū)域都在工作。故 掃描從正在工作的頂部開(kāi)始,倒著來(lái),如果額外增加一些記錄信息空間,掃描效率會(huì)高些。這 里有時(shí)、空互易問(wèn)題。保持多個(gè)表按類(lèi)型的長(zhǎng)度,每種

34、長(zhǎng)度一個(gè)表,表的元素可以交換,次序就不重要了。 這樣就不需要標(biāo)識(shí)區(qū)域的開(kāi)銷(xiāo)。歸并、重分配實(shí)現(xiàn)起來(lái)就容易得多。Ada即采用這種辦法。堆式管理除產(chǎn)生碎片之外仍有懸掛指針問(wèn)題,其原因是當(dāng)有多個(gè)指針指向同一存儲(chǔ)對(duì)象 時(shí),除配該存儲(chǔ)對(duì)象及其一個(gè)指針而忘掉消除所有指針,其它指針就是懸空(其實(shí)是指向無(wú)定義存儲(chǔ))了。兩對(duì)象共享一數(shù)據(jù)結(jié)構(gòu)時(shí)更容易出現(xiàn)。因?yàn)橐粋€(gè)死了一個(gè)活著,既要除配又不應(yīng) 除配,一除配就產(chǎn)生懸掛指針。而識(shí)別這種情況比較困難。因此,有人主張不要顯式的除配 (KILL)操作。P ascal的Dis pose備受攻擊,許多版本取消,并有返復(fù)。LIS P 語(yǔ)言就是自動(dòng)處理重用死堆對(duì)象。沒(méi)有顯式KILL命

35、令,堆對(duì)象依然要死的,但存儲(chǔ)管理不知道什么時(shí)候死。它采用無(wú)用單元回收機(jī)制,每當(dāng)堆接近滿(mǎn)的時(shí)候,從頭檢查起,如果 是靜態(tài)的、堆棧分配的它就認(rèn)為是活的,凡有一活對(duì)象指向的存儲(chǔ)也是活的。一律作上標(biāo)記。 最后將無(wú)標(biāo)記的存儲(chǔ)重新分配。盡管如此,程序員依然有義務(wù)刪除對(duì)不再需要的對(duì)象的引用(無(wú)用單元收集只解決最后全無(wú)用單元收集似乎是存儲(chǔ)管理部不用了的對(duì)象除配)。此外,無(wú)用單元收集費(fèi)時(shí)低效。隨著存儲(chǔ)廉價(jià)可配備大內(nèi)存,就不致 于在一個(gè)程序運(yùn)行期間多次運(yùn)行它。隨著機(jī)器速度進(jìn)一步提高, 有希望的出路。(Da ngli ng Referen ces),或懸掛指(KILL)時(shí)最容易產(chǎn)生。對(duì)于堆棧式管4.3懸掛引用指針指

36、向一個(gè)已死或無(wú)定義的對(duì)象,就稱(chēng)為懸掛引用 針。程序設(shè)計(jì)語(yǔ)言采用堆式管理,同時(shí)提供顯式除配命令 理,如果外塊的指針指向內(nèi)塊的存儲(chǔ)對(duì)象時(shí)也會(huì)產(chǎn)生懸掛指針,因?yàn)楫?dāng)內(nèi)塊執(zhí)行完除配,外塊 指針依然活著可用,它就指向無(wú)意義的單元的。懸掛指針是極為有害的。它甚至可以無(wú)緣無(wú)故地修改另一個(gè)程序。這對(duì)另一個(gè)程序是無(wú) 論如何也查不出的錯(cuò)。-地址操作快速索引。&”運(yùn)算符隨處使用。當(dāng)然也可以用到已除配的正因?yàn)槿绱耍赶蚨褩?nèi) (即內(nèi)塊)的指針Pascal是不允許的。Pascal只限指針為堆對(duì) 象,構(gòu)造鏈表和樹(shù)數(shù)據(jù)結(jié)構(gòu)的指針都在堆中分配。簡(jiǎn)單變量和數(shù)組在堆棧中分配,不提供地址 運(yùn)算。因此,只能用下標(biāo)處理數(shù)組,不能

37、用指針C 對(duì)指針的使用完全沒(méi)有限制。取地址“堆棧對(duì)象上。即使 C不許函數(shù)嵌套,主函數(shù)對(duì)函數(shù)的一層調(diào)用用堆棧實(shí)現(xiàn)時(shí)也會(huì)產(chǎn)生懸掛引 用,試看以下例題:例4-4懸掛引用(C)int * dan gle (int * ppp) int p=5;int m=21;*ppp=&p;return & mma in( ) int k =17int * pmpm = dan gle (&pk) /參數(shù)是指針的指針傳回的指向P的指針 返回m的地址,*pk=&k ;指向k/pk返回時(shí)pm pk均指向已失去定義的指針函數(shù) 局部量,即P和mmain()調(diào)用dangle后,堆棧中dangl

38、e所據(jù)空間可重新分配,而pm, pk指向其中的m,p就成了懸掛引用。當(dāng)然讀者會(huì)問(wèn),既然對(duì)指針如此自由使用,為什么不全由堆式管理實(shí)現(xiàn)呢?原因是堆??蓪?shí)現(xiàn)高效的遞歸。C語(yǔ)言追求高效也支持遞歸,所以采用棧-堆結(jié)合的管理方式。只好把懸掛引用的問(wèn)題留給程序員解決。C設(shè)計(jì)是為系統(tǒng)程序員用的,它的哲學(xué)是程序員不需要更多的限制。C本來(lái)就簡(jiǎn)單,提供特征不多,限制多了會(huì)成為無(wú)用的小語(yǔ)言。函數(shù)抽象作為第一類(lèi)值是另一個(gè)誘發(fā)懸掛引用的原因,請(qǐng)看以下示例:例4-5 假定Pascal將函數(shù)擴(kuò)充成第一類(lèi)對(duì)象var fv : Integer 宀 Boolean ;Real;:Integer) : Boolean ;p roce

39、dure P;var V1, V2 :fun ctio n f (mbegin V1 V2 end:=f第一類(lèi)對(duì)象/begin fvend ;beginP ;Writel n(fv(0)endP 把局部函數(shù)抽象f賦給全局函數(shù)變量fv。執(zhí)行P后引用fv(O)時(shí)等于間接引用f(0)。然而,此時(shí)V1,V2均已失去定義,fv(0)的返回值成了懸掛引用。把局部函數(shù)值作為返回值產(chǎn)生的這類(lèi)引用,在函數(shù)式語(yǔ)言把函數(shù)作為第一類(lèi)值時(shí)懸掛引用尤其嚴(yán)重。所以 ML Miranda采用的方法是把所有變量作為堆變量處理,且不提供強(qiáng)行KILL指令。所以在程序塊啟動(dòng)結(jié)束時(shí),只要(直接或間接的)引用變量還存在,那么每個(gè)變量就繼

40、續(xù)有效。但存儲(chǔ)空間利用率就沒(méi)有堆棧式分配有效了。Algol68部分解決了懸掛引用問(wèn)題:對(duì)引用賦值作些限制,弓I用局部變量不賦給比它生命長(zhǎng)的變量。在對(duì)抽象的賦值方面也作了相應(yīng)限制。但實(shí)施這些限制是增加運(yùn)行時(shí)的檢查。增加 了開(kāi)銷(xiāo)。4.4變量更新有了變量的存儲(chǔ)我們接著討論這些存儲(chǔ)里的值如何更新。變量更新實(shí)則是把存儲(chǔ)看作一 自動(dòng)機(jī)其中狀態(tài)(以共值表征)有了改變。存儲(chǔ)對(duì)象更新只有兩個(gè)途徑:賦值和初始化。也可以 用程序運(yùn)行作界線(xiàn)劃分為靜態(tài)賦值(初始化)和動(dòng)態(tài)賦值。4.4.1變量初始化變量分配了沒(méi)有定義就使用,是程序錯(cuò)誤主要來(lái)源之一。為此早期曾有自動(dòng)賦初值的做法,即分配了一律賦初值 0,或某個(gè)可區(qū)分的位模式

41、。事實(shí)證明,它不是個(gè)好辦法,不顯式賦 初值帶來(lái)查錯(cuò)困難,再者除數(shù)值變量而外,其它類(lèi)型初值約定也易產(chǎn)生誤解。變量顯式初始化由程序員在變量聲明中給出。許多語(yǔ)言都有初始化子句。以下舉出FORTRA和C兩個(gè)例子:1=1,8)/8.2 ,2.6,3.1,17.0,4* 0.0/'/'區(qū)分,先寫(xiě)變量再寫(xiě)初值,交替表示按位甚至可以寫(xiě)出更復(fù)雜的嵌套循環(huán)。后面的值 n*表示。句中例4-6 FORTRAN初始化聲明 CHARACTER * 3 EOFLAG DIMENSION A(8) DATA EOFLAG , ISUM/'NO ', 0/(A(I),ISUM偽隱含聲明的整變量。

42、FORTRAN賦初值在DATA語(yǔ)句中完成,用一對(duì) 置對(duì)應(yīng),(A(I) ,I=1.8)是循環(huán)表達(dá)的數(shù)組元素, 是與元素出現(xiàn)次序一一對(duì)應(yīng)的,多個(gè)相同連續(xù)值用no例4-7 C的初始化聲明static char en d_of_file_flag =int isum=0;(第一行)的長(zhǎng)度可由初始化值0。C稱(chēng)后者為初始化符static float a8 = 8.2, 2.6, 3.1 , 17.0;這是和上一個(gè)FORTRA例子一樣的初始化。C語(yǔ)言字符數(shù)組 表達(dá)式長(zhǎng)度定。第三行只賦了半數(shù)初值,也按位對(duì)應(yīng),其余自動(dòng)為 (ini tializer),由字面量、常量、常量表達(dá)式構(gòu)成。表達(dá)式在編譯時(shí)求值。只要復(fù)合

43、變量中一個(gè)域(元素)初始化,所有域都要初始化。唯一的偷巧是自動(dòng)為零。C 語(yǔ)言設(shè)計(jì)者認(rèn)為FORTRA初始化,有不必要的靈活性。它們比FORTRA簡(jiǎn)單直接。FORTRA有復(fù)雜的嵌套循環(huán)只能在裝載后運(yùn)行前完成。另一個(gè)現(xiàn)代風(fēng)格初始化例子見(jiàn)前述例3-4?,F(xiàn)代語(yǔ)言支持堆棧式嵌套結(jié)構(gòu)的初始化(ANSI C,C+,Ada),即給自動(dòng)變量初始化,在編譯和初裝時(shí)是無(wú)法完成的。因?yàn)檫@個(gè)堆??蚣苓€沒(méi)生成。于是由編譯算出表達(dá)式存在某個(gè)地方,并生成幾個(gè)裝載指令,待幀生成后裝入。老C版本是不支持這種自動(dòng)數(shù)組初始化的。概念上還屬于靜態(tài)初始化。4.4.2動(dòng)態(tài)更新賦值是以表達(dá)式的值強(qiáng)行置換存儲(chǔ)對(duì)象中的內(nèi)容。正因?yàn)槿绱?,才有一個(gè)存

44、儲(chǔ)對(duì)象在不 同的時(shí)間代表不同的程序?qū)ο?,這是命令式語(yǔ)言造成語(yǔ)義復(fù)雜和混亂的根源。我們稱(chēng)強(qiáng)行賦值 為破壞性賦值(Destructive Assignment) 。(1)簡(jiǎn)單賦值與聚集賦值單個(gè)變量、數(shù)組元素、記錄成分值的改變即通過(guò)簡(jiǎn)單賦值。它不涉及新值。只涉及兩個(gè) 對(duì)象:一個(gè)引用(在賦值號(hào)左邊),一個(gè)值或有值對(duì)象。多數(shù)語(yǔ)言只允許簡(jiǎn)單賦值,初始化中靜 態(tài)聚集賦值我們已介紹過(guò)了。 之間。例如,ANSI C中有:typ edef struct int age person a , b = 10 a = b ;近代語(yǔ)言幾乎都擴(kuò)充了動(dòng)態(tài)聚集賦值。但僅限于同類(lèi)型復(fù)合變量,70,/,weight ;'M

45、';char sex ; person ;/b有初值,a沒(méi)有。動(dòng)態(tài)聚集賦值,a也有了。READS程把待賦值變量作為返回參數(shù)也能實(shí)現(xiàn)賦值。此時(shí)REA實(shí)現(xiàn)一系列動(dòng)作完成賦值,沒(méi)有返回值。在這個(gè)意義LIS P及所有函(2)通過(guò)函數(shù)賦值除了強(qiáng)行賦值之外,如果通過(guò) 引用作為參數(shù),值由輸入介質(zhì)提供。 上,REA僅僅是過(guò)程,不是求值的函數(shù)。然而,真有一些語(yǔ)言賦值按函數(shù)實(shí)現(xiàn)。 數(shù)式語(yǔ)言,APL, C就采用函數(shù)賦值。例4-8 C語(yǔ)言賦值的函數(shù)實(shí)現(xiàn)#define MAXLENGTH 100float arMAXLENGTH ;int high_sub , num_elements ; high_sub=(

46、 nu m_eleme nts=MAXLENGTH)-1 ;最后一行'='號(hào)如同一般運(yùn)算符是中綴函數(shù)名,前后兩個(gè)操作數(shù)是參數(shù),它返回一個(gè)值(就是num_elements的值)。正因?yàn)槿绱?,它可以出現(xiàn)在表達(dá)式之中。LISP的'賦值'函數(shù)是replaca , replacd,返回值是對(duì)參數(shù)的引用。以下將常見(jiàn)語(yǔ)言賦值實(shí)現(xiàn)列表如下:語(yǔ)言賦值號(hào)聚集賦值多重賦值實(shí)現(xiàn)機(jī)制COBOLADDDIVII FORTRAN ALGOL:P L/1=FORTH Pascal := Ada:=MOVE(在COMPUTE句中)SUBTRACT,MULT IPLY )E可 否 否否 否 可 否

47、 可 可可 可 可否 否 可 否 否 否語(yǔ) 句LIS PrepAPL C(1973)=)lace ,rep laced某些版本可 可 否返回結(jié)果 引用 值 值函 數(shù)ANSI C表4-1常見(jiàn)語(yǔ)言賦值的實(shí)現(xiàn)機(jī)制其中多重賦值指形如a=b=c=3.0的連接賦值。C語(yǔ)言是允許的。既然函數(shù)可以動(dòng)態(tài)地改變值,則函數(shù)式語(yǔ)言每當(dāng)有破壞性賦值時(shí)就用參數(shù)束定的函數(shù)調(diào) 用替代。每當(dāng)要把一個(gè)計(jì)算值存入變量時(shí),函數(shù)式語(yǔ)言則把該值作為參數(shù)傳遞給函數(shù)。賦值例 程(或函數(shù))體內(nèi)的動(dòng)作繼續(xù)以嵌套函數(shù)實(shí)現(xiàn),這樣就避免了變量。參數(shù)束定在一個(gè)例程入口和 出口的語(yǔ)義是不變的。因而是可以得到語(yǔ)義清晰的語(yǔ)言。4.5有副作用的表達(dá)式我們把賦值

48、V: =E稱(chēng)之為賦值命令(也就是語(yǔ)句),它對(duì)表達(dá)式E求值,并以該值強(qiáng)行更 新V中的原有值。一般表達(dá)式在求值過(guò)程中是不會(huì)更新其中操作數(shù)的值的,它只引用各操作數(shù) 的值。它更新的是賦值號(hào)左邊的值。如果E是一個(gè)復(fù)合的表達(dá)式會(huì)怎樣呢?例如,其中一個(gè)子表達(dá)式是函數(shù)引用,在對(duì)E求值時(shí)必然要將此函數(shù)執(zhí)行一遍。函數(shù)體中顯然又有許多聲明和賦值語(yǔ)句,因此,除了求出函數(shù)返回值之外,其中的賦值語(yǔ)句更新了函數(shù)體中的變量。也就是除 了求函數(shù)值的主要作用而外,也起了更新變量的副作用(side effect)。如果只更新了函數(shù)局部變量,下次求該函數(shù)值時(shí)并沒(méi)有影響,如果更新了全局變量,就會(huì)影響到其它表達(dá)式求值, 這就難于控制了。

49、4.5.1塊表達(dá)式塊(Block)是嵌套在程序中的局部程序,由封閉的聲明和語(yǔ)句集組成。如果表達(dá)式包含的 子表達(dá)式是一個(gè)聲明或定義,該表達(dá)式即為塊表達(dá)式。前述函數(shù)引用是隱式的塊表達(dá)式,函數(shù) 體中既有聲明又有語(yǔ)句。如果該函數(shù)沒(méi)有副作用則與變量引用無(wú)異,如果有則以塊表達(dá)式看 待。有些語(yǔ)言具有顯式塊表達(dá)式,如ML例4-9 已知三角形三邊a,b,c的長(zhǎng)度求三角形的面積:let val s = (a+b+c)*0.5in sqrt(s*(s-a)*(s-b)*(s-c)end抽象形式是let D in E end ,其中D是說(shuō)明E的表達(dá)式,一起叫塊表達(dá)式。D的作用僅限于說(shuō)明E。,C語(yǔ)言有顯式的命令4.5.

50、2命令表達(dá)式如果沒(méi)有聲明,塊中只有命令,嵌入到表達(dá)式中則為命令表達(dá)式 表達(dá)式:例4-10 C語(yǔ)言讀字符常見(jiàn)的表達(dá)式(c=getchar( )!=EOF該表達(dá)式求真值,若 C中讀入是EOF本表達(dá)式求值為0(假)。其它字符時(shí)值為1(真)。子表達(dá)式 c=getchar() 是賦值命令。故本表達(dá)式是 (嵌入了)命令(的)表達(dá)式。這個(gè)getchar() 肯定是有副作用的。因?yàn)閮纱瓮瑯訜o(wú)參調(diào)用可得出不同的字符。從當(dāng)前讀的位置移到下一個(gè)字符位置就是副作用,但這個(gè)副作用正是C程序員希望的。塊表達(dá)式和命令表達(dá)式都是具有副作用的表達(dá)式,我們擴(kuò)充它們無(wú)非是希望它的副作用 能增強(qiáng)表達(dá)能力,事實(shí)上有了它們程序清晰可讀,

51、如上面的例子。ML沒(méi)有全局量不會(huì)有什么壞的副作用。C語(yǔ)言就靠程序員保證了。4.6小結(jié)程序變量具有時(shí)、空特性,故引入存儲(chǔ)對(duì)象概念。它既是程序?qū)ο蟮膶?shí)現(xiàn)又是獨(dú)立的 存儲(chǔ)體。變量名字編譯后成為地址碼,將此碼存放在存儲(chǔ)對(duì)象中,該存儲(chǔ)對(duì)象對(duì)應(yīng)的程序?qū)ο?叫引用(C+),引用變量是該變量的別名。指針可以存放任何程序?qū)ο蟮牡刂?,由程序員操 縱。通過(guò)指針變量引用程序?qū)ο蟮闹到羞f引用。變量有分配、未分配、除分配,定義、未定義、失去定義的因時(shí)而變的狀態(tài)。存儲(chǔ)模型有兩大類(lèi):靜態(tài)存儲(chǔ)和動(dòng)態(tài)存儲(chǔ)。靜態(tài)存儲(chǔ)在運(yùn)行前分配變量的存儲(chǔ),動(dòng)態(tài) 存儲(chǔ)在運(yùn)行中分配和除配。動(dòng)態(tài)存儲(chǔ)模型又分兩類(lèi):堆存儲(chǔ)和堆棧存儲(chǔ)。堆棧存貯,堆存貯模型是

52、單今多數(shù)模塊語(yǔ)言模型。存儲(chǔ)對(duì)象是有其生命周期的。一般局限于生成該存儲(chǔ)對(duì)象所在模塊的生命期。程序員 可顯式控制存儲(chǔ)對(duì)象的生命期,在一模塊內(nèi)使用多生命期對(duì)象,以程序運(yùn)行周期為準(zhǔn),大于此 周期為持久變量,等于為全局,小于為局部,局部尚可內(nèi)嵌局部。除持久對(duì)象外其余隨程序運(yùn) 行終止而銷(xiāo)毀,均為臨時(shí)的。靜態(tài)存儲(chǔ)對(duì)象一般由編譯分配后在整個(gè)程序執(zhí)行期間不會(huì)改變。全局的、外部的變量 均靜態(tài)對(duì)象。也有語(yǔ)言將局部變量定為靜態(tài)的。動(dòng)態(tài)存儲(chǔ)對(duì)象往往取決于輸入值和程序執(zhí)行情況無(wú)法在編譯時(shí)確定所需存儲(chǔ),只能動(dòng) 態(tài)分配/除配。堆棧式管理和程序模塊執(zhí)行自然相適應(yīng),且天然支持遞歸,高效。堆式管理自由方便,存儲(chǔ)利用相對(duì)耗費(fèi)大。多數(shù)

53、語(yǔ)言是兩者相結(jié)合。指針對(duì)象一般是堆對(duì)象。顯式除配操作易引起懸掛指針,外塊指針指向內(nèi)塊對(duì)象也易引起懸掛。懸掛指針是程 序出錯(cuò)根源之一。一般認(rèn)為命令式語(yǔ)言有三大害:任意的goto語(yǔ)句、懸掛指針、函數(shù)副作用,本章涉及一個(gè)半。函數(shù)副作用第 6章還要講。程序中變量更新只有兩個(gè)途徑 :賦值和初始化。賦值通過(guò)賦值命令或調(diào)用讀例程。調(diào)用 例程或函數(shù)是避免破壞性賦值重要途徑。是函數(shù)式語(yǔ)言得以發(fā)展的原因。當(dāng)今語(yǔ)言賦值有按賦值命令模型有按函數(shù)調(diào)用模型。C語(yǔ)言按函數(shù)模型故有賦值命令出現(xiàn)在表達(dá)式中的命令表達(dá)式。如果一個(gè)程序塊(分程序)嵌入在表達(dá)式中則稱(chēng)塊表達(dá)式。函數(shù)體是塊表達(dá)式,嵌有函 數(shù)引用的表達(dá)式是隱式塊表達(dá)式,M

54、L有顯式塊表達(dá)式。如果塊中只有命令沒(méi)有聲明即為命令表達(dá)式。擴(kuò)充它們是為了擴(kuò)大表達(dá)能力,但也會(huì) 帶來(lái)副作用,故稱(chēng)有副作用的表達(dá)式。好的函數(shù)副作用使程序簡(jiǎn)單清晰,一旦放開(kāi)了函數(shù)副作用又易產(chǎn)生難調(diào)易出錯(cuò)問(wèn)題。 這取決于語(yǔ)言設(shè)計(jì)的宗旨。但目前所有命令式語(yǔ)言都有一定程度的的函數(shù)副作用。習(xí)題4.1 一般變量和指針變量有何不同?引用類(lèi)型變量與它們又有何不同?答:在尋址對(duì)照表中,一般變量對(duì)應(yīng)的地址碼即為該變量值的存儲(chǔ)單元,而指針變量 對(duì)應(yīng)的地址碼則為該指針?biāo)赶虻拇鎯?chǔ)單元的地址。引用型變量與指針變量的區(qū)別在于它是常指針,一旦對(duì)照表建立,也就創(chuàng)建了引用, 不可改變。引用是變量的別名,但它必須賦初值。4.2 C語(yǔ)

55、言中數(shù)組名的含義是什么,當(dāng)把數(shù)組的地址賦給某指針時(shí)是否要用算符,為什么答:C語(yǔ)言中的數(shù)組名可以看作是一個(gè)指針變量名,它代表數(shù)組元素的首地址。但其值 不可改變,這一點(diǎn)與引用型變量有些類(lèi)似。當(dāng)把數(shù)組的地址賦給某指針時(shí)不用運(yùn)算符,因?yàn)閿?shù) 組名本身就是指向數(shù)組首地址的指針。?哪些動(dòng)態(tài)實(shí)現(xiàn)?4.3 現(xiàn)今語(yǔ)言有靜態(tài)數(shù)組、動(dòng)態(tài)定義數(shù)組、可變長(zhǎng)靈活數(shù)組,試說(shuō)出它們的定義,并舉 出哪種語(yǔ)言采用它的例子 (一個(gè)語(yǔ)言也行)。4.4 找一個(gè)你所熟悉的語(yǔ)言所寫(xiě)的程序,指出哪些變量應(yīng)用靜態(tài)實(shí)現(xiàn) 該語(yǔ)言真是這樣的嗎?4.5 試總結(jié)存儲(chǔ)對(duì)象生命期有哪幾種,C語(yǔ)言與此對(duì)應(yīng)各叫什么變量4.6 何謂運(yùn)行時(shí)堆棧?堆棧幀?堆?答:運(yùn)行時(shí)堆棧是在內(nèi)存開(kāi)辟一個(gè)空間,如同堆棧。首先將程序代碼和全局靜態(tài)變量裝 入。在執(zhí)行控制開(kāi)始時(shí)首先

溫馨提示

  • 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶(hù)所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶(hù)上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶(hù)上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶(hù)因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

最新文檔

評(píng)論

0/150

提交評(píng)論