“text segment ”是應(yīng)用程序運(yùn)行時應(yīng)用程序代碼存在的內(nèi)存段膳沽。每一個指令分苇,每一個單個函數(shù)崔列、過程梢褐、方法和執(zhí)行代碼都存在這個內(nèi)存段中直到應(yīng)用程序退出。一般情況下赵讯,你不會真的不得不知道這個段的任何事情盈咳。
當(dāng)應(yīng)用開始以后,函數(shù)main() 被調(diào)用边翼,一些空間分配在”stack” 中鱼响。這是為應(yīng)用分配的另一個段的內(nèi)存空間,這是為了函數(shù)變量存儲需要而分配的內(nèi)存组底。每一次在應(yīng)用中調(diào)用一個函數(shù)丈积,“stack ”的一部分會被分配在”stack” 中,稱之為”frame” 斤寇。新函數(shù)的本地變量分配在這里桶癣。
正如名稱所示拥褂,“stack ”是后進(jìn)先出(LIFO )結(jié)構(gòu)娘锁。當(dāng)函數(shù)調(diào)用其他的函數(shù)時,“stack frame ”會被創(chuàng)建饺鹃;當(dāng)其他函數(shù)退出后莫秆,這個“frame ”會自動被破壞间雀。
“heap” 段也稱為”data” 段,提供一個保存中介貫穿函數(shù)的執(zhí)行過程镊屎,全局和靜態(tài)變量保存在“heap ”中惹挟,直到應(yīng)用退出。
為了訪問你創(chuàng)建在heap 中的數(shù)據(jù)缝驳,你最少要求有一個保存在stack 中的指針连锯,因?yàn)槟愕腃PU 通過stack 中的指針訪問heap 中的數(shù)據(jù)。
你可以認(rèn)為stack 中的一個指針僅僅是一個整型變量用狱,保存了heap 中特定內(nèi)存地址的數(shù)據(jù)运怖。實(shí)際上,它有一點(diǎn)點(diǎn)復(fù)雜夏伊,但這是它的基本結(jié)構(gòu)摇展。
簡而言之,操作系統(tǒng)使用stack 段中的指針值訪問heap 段中的對象溺忧。如果stack 對象的指針沒有了咏连,則heap 中的對象就不能訪問。這也是內(nèi)存泄露的原因鲁森。
在iOS 操作系統(tǒng)的stack 段和heap 段中祟滴,你都可以創(chuàng)建數(shù)據(jù)對象。
stack 對象的優(yōu)點(diǎn)主要有兩點(diǎn)歌溉,一是創(chuàng)建速度快踱启,二是管理簡單,它有嚴(yán)格的生命周期研底。stack 對象的缺點(diǎn)是它不靈活埠偿。創(chuàng)建時長度是多大就一直是多大,創(chuàng)建時是哪個函數(shù)創(chuàng)建的榜晦,它的owner 就一直是它冠蒋。不像heap 對象那樣有多個owner ,其實(shí)多個owner 等同于引用計(jì)數(shù)乾胶。只有heap 對象才是采用“引用計(jì)數(shù)”方法管理它抖剿。
stack 對象的創(chuàng)建
只要棧的剩余空間大于stack 對象申請創(chuàng)建的空間,操作系統(tǒng)就會為程序提供這段內(nèi)存空間识窿,否則將報異常提示棧溢出斩郎。
heap 對象的創(chuàng)建
操作系統(tǒng)對于內(nèi)存heap 段是采用鏈表進(jìn)行管理的。操作系統(tǒng)有一個記錄空閑內(nèi)存地址的鏈表喻频,當(dāng)收到程序的申請時缩宜,會遍歷鏈表,尋找第一個空間大于所申請的heap 節(jié)點(diǎn),然后將該節(jié)點(diǎn)從空閑節(jié)點(diǎn)鏈表中刪除锻煌,并將該節(jié)點(diǎn)的空間分配給程序妓布。
例如:
NSString 的對象就是stack 中的對象,NSMutableString 的對象就是heap 中的對象宋梧。前者創(chuàng)建時分配的內(nèi)存長度固定且不可修改匣沼;后者是分配內(nèi)存長度是可變的,可有多個owner, 適用于計(jì)數(shù)管理內(nèi)存管理模式捂龄。
兩類對象的創(chuàng)建方法也不同释涛,前者直接創(chuàng)建“NSString * str1=@"welcome"; “,而后者需要先分配再初始化“ NSMutableString * mstr1=[[NSMutableString alloc] initWithString:@"welcome"]; ”倦沧。
再補(bǔ)充一點(diǎn)枢贿,這里說的是操作系統(tǒng)的堆和棧。
在我們學(xué)習(xí)“數(shù)據(jù)結(jié)構(gòu)”時刀脏,接觸到的堆和棧的概念和這個操作系統(tǒng)中的堆和棧不是一回事的局荚。
操作系統(tǒng)的堆和棧是指對內(nèi)存進(jìn)行操作和管理的一些方式。
“數(shù)據(jù)結(jié)構(gòu)“的堆實(shí)際上指的就是(滿足堆性質(zhì)的)優(yōu)先Queue 的一種數(shù)據(jù)結(jié)構(gòu)愈污,第1 個元素有最高的優(yōu)先權(quán)耀态;棧實(shí)際上就是滿足先進(jìn)后出的性質(zhì)的數(shù)據(jù)或數(shù)據(jù)結(jié)構(gòu)。
一暂雹、堆和棧的概念區(qū)別
堆: 是大家共有的空間首装,分全局堆和局部堆。全局堆就是所有沒有分配的空間杭跪,局部堆就是用戶分配的空間仙逻。堆在操作系統(tǒng)對進(jìn)程 初始化的時候分配,運(yùn)行過程中也可以向系統(tǒng)要額外的堆涧尿,但是記得用完了要還給操作系統(tǒng)系奉,要不然就是內(nèi)存泄漏。堆里面一般 放的是靜態(tài)數(shù)據(jù)姑廉,比如static的數(shù)據(jù)和字符串常量等缺亮,資源加載后一般也放在堆里面。一個進(jìn)程的所有線程共有這些堆 桥言,所以對堆的操作要考慮同步和互斥的問題萌踱。程序里面編譯后的數(shù)據(jù)段都是堆的一部分。
棧: 是個線程獨(dú)有的号阿,保存其運(yùn)行狀態(tài)和局部自動變量的并鸵。棧在線程開始的時候初始化,每個線程的椚咏В互相獨(dú)立园担,因此 ,棧是 thread safe的。每個c++對象的數(shù)據(jù)成員也存在在棧中粉铐,每個函數(shù)都有自己的棧,棧被用來在函數(shù)之間傳遞參數(shù)卤档。操作系統(tǒng)在切換線程的時候會自動的切換棧蝙泼,就是 切換ss/esp寄存器。椚霸妫空間不需要在高級語言里面顯式的分配 和釋放汤踏。
預(yù)備知識—程序的內(nèi)存分配
一個由c/C++編譯的程序占用的內(nèi)存分為以下幾個部分:
1、棧區(qū)(stack)— 由編譯器自動分配釋放舔腾,存放函數(shù)的參數(shù)值溪胶,局部變量的值等。其操作方式類似于數(shù)據(jù)結(jié)構(gòu)中的棧稳诚。
2哗脖、堆區(qū)(heap) — 一般由程序員分配釋放, 若程序員不釋放扳还,程序結(jié)束時可能由OS回收才避。注意它與數(shù)據(jù)結(jié)構(gòu)中的堆是兩回事,分配方式倒是類似于鏈表氨距。
3桑逝、全局區(qū)(靜態(tài)區(qū))(static)—,全局變量和靜態(tài)變量的存儲是放在一塊的俏让,初始化的全局變量和靜態(tài)變量在一塊區(qū)域楞遏,未初始化的全局變量和未初始化的靜態(tài)變量在相鄰的另一塊區(qū)域。程序結(jié)束后由系統(tǒng)釋放首昔。
4寡喝、文字常量區(qū) —常量字符串就是放在這里的。程序結(jié)束后由系統(tǒng)釋放勒奇。
5拘荡、程序代碼區(qū)—存放函數(shù)體的二進(jìn)制代碼。
二撬陵、例子程序
//main.cpp
int a = 0; 全局初始化區(qū)
char *p1; 全局未初始化區(qū)
main()
{
int b; //棧
char s[] = "abc"; //棧
char *p2; //棧
char *p3 = "123456"; //123456{row.content}在常量區(qū)珊皿,p3在棧上。
static int c =0巨税; //全局(靜態(tài))初始化區(qū)
p1 = (char *)malloc(10);
p2 = (char *)malloc(20);//分配得來的10和20字節(jié)的區(qū)域就在堆區(qū)蟋定。
strcpy(p1, "123456"); //123456{row.content}放在常量區(qū),編譯器可能會將它與p3所指向的"123456"優(yōu)化成一個地方草添。
}
三驶兜、堆和棧的理論知識
1.申請方式
stack:由系統(tǒng)自動分配。例如,聲明在函數(shù)中一個局部變量 int b; 系統(tǒng)自動在棧中為b開辟空間
heap:需要程序員自己申請抄淑,并指明大小屠凶,在c中malloc函數(shù)
如p1 = (char *)malloc(10);
在C++中用new運(yùn)算符
如p2 = (char *)malloc(10);
但是注意p1、p2本身是在棧中的肆资。
2.申請后系統(tǒng)的響應(yīng)
棧:只要棧的剩余空間大于所申請空間矗愧,系統(tǒng)將為程序提供內(nèi)存,否則將報異常提示棧溢出郑原。
堆:首 先應(yīng)該知道操作系統(tǒng)有一個記錄空閑內(nèi)存地址的鏈表唉韭,當(dāng)系統(tǒng)收到程序的申請時,會遍歷該鏈表犯犁,尋找第一個空間大于所申請空間的堆結(jié)點(diǎn)属愤,然后將該結(jié)點(diǎn)從空閑結(jié) 點(diǎn)鏈表中刪除,并將該結(jié)點(diǎn)的空間分配給程序酸役,另外住诸,對于大多數(shù)系統(tǒng),會在這塊內(nèi)存空間中的首地址處記錄本次分配的大小涣澡,這樣只壳,代碼中的delete語句才 能正確的釋放本內(nèi)存空間。另外暑塑,由于找到的堆結(jié)點(diǎn)的大小不一定正好等于申請的大小吼句,系統(tǒng)會自動的將多余的那部分重新放入空閑鏈表中。
3.申請大小的限制
棧: 在Windows下,棧是向低地址擴(kuò)展的數(shù)據(jù)結(jié)構(gòu)事格,是一塊連續(xù)的內(nèi)存的區(qū)域惕艳。這句話的意思是棧頂?shù)牡刂泛蜅5淖畲笕萘渴窍到y(tǒng)預(yù)先規(guī)定好的,在 WINDOWS下驹愚,棧的大小是2M(也可能是1M远搪,它是一個編譯時就確定的常數(shù)),如果申請的空間超過棧的剩余空間時逢捺,將提示overflow谁鳍。因此,能 從棧獲得的空間較小劫瞳。堆:堆是向高地址擴(kuò)展的數(shù)據(jù)結(jié)構(gòu)倘潜,是不連續(xù)的內(nèi)存區(qū)域。這是由于系統(tǒng)是用鏈
表來存儲的空閑內(nèi)存地址的志于,自然是不連續(xù)的涮因,而鏈表的遍歷方向是由低地址向高地址。堆的大小受限于計(jì)算機(jī)系統(tǒng)中有效的虛擬內(nèi)存伺绽。由此可見养泡,堆獲得的空間比較靈活嗜湃,也比較大。
4.申請效率的比較:
棧由系統(tǒng)自動分配澜掩,速度較快购披。但程序員是無法控制的。棧是機(jī)器系統(tǒng)提供的數(shù)據(jù)結(jié)構(gòu)肩榕,計(jì)算機(jī)會在底層對棧提供支持:分配專門的寄存器存放棧的地址刚陡,壓棧出棧都有專門的指令執(zhí)行,這就決定了棧的效率比較高点把。
堆是由new分配的內(nèi)存橘荠,一般速度比較慢屿附,而且容易產(chǎn)生內(nèi)存碎片,不過用起來最方便.
另外郎逃,在WINDOWS下,最好的方式是用VirtualAlloc分配內(nèi)存挺份,他不是在堆褒翰,也不是在棧是直接在進(jìn)程的地址空間中保留一快內(nèi)存,雖然用起來最不方便匀泊。但是速度快优训,也最靈活。堆則是C/C++函數(shù)庫提供的各聘,它的機(jī)制是很復(fù)雜的
5.堆和棧中的存儲內(nèi)容
棧: 在函數(shù)調(diào)用時揣非,第一個進(jìn)棧的是主函數(shù)中后的下一條指令(函數(shù)調(diào)用語句的下一條可執(zhí)行語句)的地址,然后是函數(shù)的各個參數(shù)躲因,在大多數(shù)的C編譯器中早敬,參數(shù)是由右往左入棧的,然后是函數(shù)中的局部變量大脉。注意靜態(tài)變量是不入棧的搞监。
當(dāng)本次函數(shù)調(diào)用結(jié)束后,局部變量先出棧镰矿,然后是參數(shù)琐驴,最后棧頂指針指向最開始存的地址,也就是主函數(shù)中的下一條指令秤标,程序由該點(diǎn)繼續(xù)運(yùn)行绝淡。
堆:一般是在堆的頭部用一個字節(jié)存放堆的大小。堆中的具體內(nèi)容有程序員安排
6.存取效率的比較
char s1[] = "aaaaaaaaaaaaaaa";
char *s2 = "bbbbbbbbbbbbbbbbb";
aaaaaaaaaaa是在運(yùn)行時刻賦值的苍姜;
而bbbbbbbbbbb是在編譯時就確定的够委;
但是,在以后的存取中怖现,在棧上的數(shù)組比指針?biāo)赶虻淖址?例如堆)快茁帽。
比如:
void main()
{
char a = 1;
char c[] = "1234567890";
char *p ="1234567890";
a = c[1];
a = p[1];
return;
}
對應(yīng)的匯編代碼
10: a = c[1];
00401067 8A 4D F1 mov cl,byte ptr [ebp-0Fh]
0040106A 88 4D FC mov byte ptr [ebp-4],cl
11: a = p[1];
0040106D 8B 55 EC mov edx,dword ptr [ebp-14h]
00401070 8A 42 01 mov al,byte ptr [edx+1]
00401073 88 45 FC mov byte ptr [ebp-4],al
第一種在讀取時直接就把字符串中的元素讀到寄存器cl中玉罐,而第二種則要先把指針值讀到edx中,在根據(jù)edx讀取字符潘拨,顯然慢了吊输。
7小結(jié):
堆和棧的區(qū)別可以用如下的比喻來看出:
使用棧就象我們?nèi)ワ堭^里吃飯,只管點(diǎn)菜(發(fā)出申請)铁追、付錢季蚂、和吃(使用),吃飽了就走琅束,不必理會切菜扭屁、洗菜等準(zhǔn)備工作和洗碗、刷鍋等掃尾工作涩禀,他的好處是快捷料滥,但是自由度小。
使用堆就象是自己動手做喜歡吃的菜肴艾船,比較麻煩葵腹,吃完之后還得清洗、打掃等后續(xù)工作(不然用完了不清洗就不好再用)屿岂,但是比較符合自己的口味践宴,而且自由度大。
管理方式:對于棧來講爷怀,是由編譯器自動管理阻肩,無需我們手工控制;對于堆來說运授,釋放工作由程序員控制烤惊,容易產(chǎn)生memory leak。
申請大型狡隆:
棧:在Windows下,棧是向低地址擴(kuò)展的數(shù)據(jù)結(jié)構(gòu)撕氧,是一 塊連續(xù)的內(nèi)存的區(qū)域。這句話的意思是棧頂?shù)牡刂泛蜅5淖畲笕萘渴窍到y(tǒng)預(yù)先規(guī)定好的喇完,在 WINDOWS下伦泥,棧的大小是2M(也有的說是1M,總之是一個編譯時就確定的常數(shù))锦溪,如果申請的空間超過棧的剩余空間時苫昌,將提示overflow惭蟋。因 此舵揭,能從棧獲得的空間較小如筛。
堆:堆是向高地址擴(kuò)展的數(shù)據(jù)結(jié)構(gòu),是不連續(xù)的內(nèi)存區(qū)域则涯。這是由于系統(tǒng)是用鏈表來存儲的空閑內(nèi)存地址的复局,自然是不連續(xù)的冲簿,而鏈表的遍歷方向是由低地址向高地址。堆的大小受限于計(jì)算機(jī)系統(tǒng)中有效的虛擬內(nèi)存亿昏。由此可見峦剔,堆獲得的空間比較靈活,也比較大角钩。
碎片問題:對于堆來講吝沫,頻繁的new/delete勢必會造成內(nèi)存空間的不連續(xù),從而造成大量的碎片递礼,使程序效率降低惨险。對于棧來講,則不會存在這個問題脊髓,因?yàn)闂J窍冗M(jìn)后出的隊(duì)列辫愉,他們是如此的一一對應(yīng),以至于永遠(yuǎn)都不可能有一個內(nèi)存塊從棧中間彈出
分配方式:堆都是動態(tài)分配的供炼,沒有靜態(tài)分配的堆一屋。
棧有2種分配方式:靜態(tài)分配和動態(tài)分配窘疮。靜態(tài)分配是編譯器完成的袋哼,比如局部變量的分配。動態(tài)分配由alloca函數(shù)進(jìn)行分配闸衫,但是棧的動態(tài)分配和堆是不同的涛贯,他的動態(tài)分配是由編譯器進(jìn)行釋放,無需我們手工實(shí)現(xiàn)蔚出。
分配效率:棧是機(jī)器系統(tǒng)提供的數(shù)據(jù)結(jié)構(gòu)弟翘,計(jì)算機(jī)會在底層對棧提供支持:分配專門的寄存器存放棧的地址,壓棧出棧都有專門的指令執(zhí)行骄酗,這就決定了棧的效率比較高稀余。堆則是C/C++函數(shù)庫提供的,它的機(jī)制是很復(fù)雜的趋翻。