iOS中的堆(heap)和棧(stack)的理解

“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ù)雜的趋翻。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末睛琳,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子踏烙,更是在濱河造成了極大的恐慌师骗,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件讨惩,死亡現(xiàn)場離奇詭異辟癌,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)荐捻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進(jìn)店門黍少,熙熙樓的掌柜王于貴愁眉苦臉地迎上來寡夹,“玉大人,你說我怎么就攤上這事厂置∫觯” “怎么了?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵农渊,是天一觀的道長患蹂。 經(jīng)常有香客問我,道長砸紊,這世上最難降的妖魔是什么传于? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮醉顽,結(jié)果婚禮上沼溜,老公的妹妹穿的比我還像新娘。我一直安慰自己游添,他們只是感情好系草,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著唆涝,像睡著了一般找都。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上廊酣,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天能耻,我揣著相機(jī)與錄音,去河邊找鬼亡驰。 笑死晓猛,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的凡辱。 我是一名探鬼主播戒职,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼透乾!你這毒婦竟也來了洪燥?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤续徽,失蹤者是張志新(化名)和其女友劉穎蚓曼,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體钦扭,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡纫版,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了客情。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片其弊。...
    茶點(diǎn)故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡癞己,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出梭伐,到底是詐尸還是另有隱情痹雅,我是刑警寧澤,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布糊识,位于F島的核電站绩社,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏赂苗。R本人自食惡果不足惜愉耙,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望拌滋。 院中可真熱鬧朴沿,春花似錦、人聲如沸败砂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽昌犹。三九已至坚芜,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間祭隔,已是汗流浹背货岭。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工路操, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留疾渴,地道東北人。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓屯仗,卻偏偏與公主長得像搞坝,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子魁袜,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內(nèi)容