程序的環(huán)境由內(nèi)存、運行庫和系統(tǒng)調(diào)用(API)等構(gòu)成柔昼。
系統(tǒng)調(diào)用和API是程序與內(nèi)核之間交互的中介。
C/C++語言中指針的大小是4個字節(jié)是有原因的,因為指針實際上是個地址值拟淮,地址值決定于內(nèi)存空間大小,而CPU支持的最大內(nèi)存容量是由地址總線的寬度決定的谴忧,因為32位地址總線的寬度是32位很泊,即4字節(jié)角虫,所以指針的大小也是4字節(jié)。
內(nèi)存中有一部分稱為內(nèi)核空間委造,普通的應(yīng)用程序是無法訪問的戳鹅。
除此之外,內(nèi)存還有椈枵祝空間枫虏、堆空間、可執(zhí)行映像亮垫、保留區(qū)模软。
棧:用于維護函數(shù)調(diào)用的上下文。
堆:用于動態(tài)分配空間饮潦,它通常位于棧的下方燃异。
可執(zhí)行映像:用于裝載可執(zhí)行文件在內(nèi)存中的映像。
保留區(qū):內(nèi)存中受保護而禁止訪問的區(qū)域继蜡,如NULL回俐。
內(nèi)存中還有一段動態(tài)鏈接庫映射區(qū),用于裝載動態(tài)鏈接庫稀并。
椊銎模總是向低地址方向增長的。
棧最重要的功能是保存一個函數(shù)調(diào)用所需要的信息碘举,這信息通常被叫做堆棧幀或活動記錄忘瓦。
堆棧幀一般包括:
1、函數(shù)的返回地址和參數(shù)引颈。
2耕皮、臨時變量,包括非晶態(tài)局部變量和編譯期自動生成的臨時變量蝙场。
3凌停、保存的上下文,包括函數(shù)調(diào)用前后需要保持不變的寄存器售滤。
ESP寄存器總是指向棧頂罚拟,EBP(棧指針)寄存器總是指向函數(shù)活動記錄的固定位置。
P313描述了i386函數(shù)調(diào)用的過程完箩。
為什么未初始化的數(shù)組元素被打印出來以后是漢字的“燙”赐俗?
這是因為它們在DEBUG模式下被初始化為0xCC,而兩個連續(xù)的0xCC就是0xCCCC嗜憔,即秃励,漢字的“燙”。
還有的時候是使用0xCD初始化未初始化的數(shù)組吉捶,這時候顯示的漢字是“屯”夺鲜。
“鉤子”函數(shù)可以通過替換掉Windows函數(shù)的匯編序列前面幾條“無用”的指令實現(xiàn)。
是指函數(shù)的調(diào)用方和被調(diào)用方都遵守同樣的函數(shù)調(diào)用方式呐舔。
它一般規(guī)定如下幾個方面的內(nèi)容:
1币励、函數(shù)參數(shù)的傳遞方式和順序。大部分函數(shù)參數(shù)是通過棧傳遞珊拼,有些是通過寄存器食呻。
2、棧的維護方式澎现。就是調(diào)用完了就把被調(diào)方全部彈出棧仅胞。
3、名字修飾策略剑辫。調(diào)用慣例會對函數(shù)本身的名字進行修飾干旧。
_cdecl就是一種調(diào)用慣例。
棧在函數(shù)調(diào)用過程中的變化是這樣的:
1妹蔽、首先將參數(shù)壓入棧中椎眯。
2、繼續(xù)壓入被調(diào)函數(shù)在主調(diào)函數(shù)中的下一條指令的地址胳岂,改地址在內(nèi)存中就是返回地址编整。比如:
現(xiàn)在假設(shè)f是被調(diào)函數(shù),那么return
0的地址就是要被壓入棧中的地址乳丰。
3掌测、跳轉(zhuǎn)到函數(shù)體執(zhí)行。
現(xiàn)在就P322的圖說明幾點問題产园。
old ebp是啥意思汞斧?首先要明確圖中的意思是個值,即ebp寄存器中的值淆两,這個值有著特殊的意義断箫。這個值是個地址,而這個地址是當前函數(shù)的調(diào)用函數(shù)的ebp的值在內(nèi)存中的位置秋冰。所以這就是個無限循環(huán)仲义。
EBP(Extended Base Pointer):擴展基址指針寄存器。
由此可見EBP其實起到了一個基址的作用剑勾,它通常與ESP(Extended
Stack Pointer)搭配使用埃撵,前者是基址,后者是偏移量虽另,這樣的話就可以指向某函數(shù)的寄存器和局部變量了暂刘。
保存這個舊的EBP值的作用在于它能夠恢復(fù)調(diào)用函數(shù)的EBP值,調(diào)用函數(shù)的EBP值是個絕對地址捂刺,如果你不恢復(fù)它谣拣,那么調(diào)用函數(shù)就無法再利用ESP的值來指向它自己的寄存器和全局變量了募寨。
C++中的this指針存放在ECX(Extended
Counter miX,擴展計數(shù)寄存器)中森缠。
EAX(Extended Accumulator miX拔鹰,擴展累加寄存器)負責(zé)傳遞返回值,它能返回4字節(jié)的返回值贵涵。
對于小于等于8字節(jié)并且大于4字節(jié)的返回值列肢,可以搭配EDX(Extended
Data miX,擴展數(shù)據(jù)寄存器)進行返回宾茂。
如果返回值太大瓷马,那就必須開辟一段棧空間來臨時存放返回值跨晴。首先產(chǎn)生返回值的函數(shù)會把返回值寫到這段空間中欧聘,然后這段空間中的值再寫到真正的用于裝載這個返回值的變量中,這個變量是我們常說的返回值坟奥,至于該段中所說的返回值就是指需要被返回的數(shù)據(jù)树瞭。也就是說在這個過程中會產(chǎn)生兩次復(fù)制。
堆的處理過程比棧要復(fù)雜爱谁,因為程序在執(zhí)行過程中隨時可能申請空間和釋放空間晒喷,而且申請和釋放的空間大小也不確定。
堆是一塊巨大的內(nèi)存空間访敌,占據(jù)了虛擬內(nèi)存的絕大部分凉敲。
堆可以將數(shù)據(jù)傳遞到函數(shù)外面,而且能動態(tài)地產(chǎn)生對象寺旺。
直接操作堆空間的是程序的運行庫爷抓,而不是系統(tǒng)調(diào)用,因為這樣做比較高效阻塑。
有兩種方式:
1蓝撇、通過調(diào)整數(shù)據(jù)段的結(jié)束地址來調(diào)整堆空間的大小。
2陈莽、直接申請一段內(nèi)存空間用作堆空間渤昌。
申請的空間的起始地址和大小都是頁大小的整數(shù)倍。
在Linux系統(tǒng)下走搁,堆可以存在于從BSS段到共享庫裝載的位置和從共享庫結(jié)束位置到棧這兩部分空間独柑,現(xiàn)在的話能有2.9G大小。也就是說現(xiàn)在你用malloc申請堆空間至多能申請2.9G左右私植。
Windows下的堆和椉烧ぃ空間是非常零散的,因為每個線程在建立的時候都需要自己的椙冢空間索绪,堆空間就是在剩下的零碎的空間中分配的湖员。
它的對空間大小和起始地址也必須是頁的整數(shù)倍。
無論是Linux還是Windows都有自己的堆分配算法者春。
每個進程在創(chuàng)建的時候破衔,系統(tǒng)都會給它分配一個堆清女,并在進程結(jié)束的時候銷毀堆钱烟。
堆空間并不是連續(xù)的。
堆空間不一定都是向上增長的嫡丙。
它就是負責(zé)管理堆空間的申請拴袭、分配和釋放的算法。
1曙博、空閑鏈表法拥刻。
2、位圖父泳。把整個堆劃分成大小相同的塊般哼,第一個塊叫頭,其余的稱為主體惠窄,用一個數(shù)組來記錄塊的使用情況蒸眠,每個塊有三種狀態(tài),即杆融,頭楞卡、主體和空閑。
3脾歇、對象池蒋腮。如果每次對象申請的堆空間大小相同,就可以把堆劃分成對象大小的若干塊藕各,每次只需要取一塊分配出去即可池摧。