0. 問(wèn)題:C語(yǔ)言中的數(shù)據(jù)存儲(chǔ)區(qū)有哪些?
1. 程序中的棧
棧是現(xiàn)代計(jì)算機(jī)程序里最為重要的概念狂魔,其作用是用于維護(hù)函數(shù)調(diào)用上下文,也就是說(shuō)在我們程序運(yùn)行的時(shí)候必須要使用的存儲(chǔ)區(qū)就是棧存儲(chǔ)區(qū),因?yàn)镃程序是一個(gè)接著一個(gè)的函數(shù)調(diào)用锅论,并且C程序是從main函數(shù)開(kāi)始,既然使用了main函數(shù)令宿,必然就是使用棧存儲(chǔ)區(qū)(因?yàn)闂5淖饔镁褪蔷S護(hù)函數(shù)調(diào)用的上下文)叼耙。 棧存儲(chǔ)區(qū)主要用來(lái)保存函數(shù)調(diào)用的時(shí)所需要的參數(shù)信息、局部變量信息粒没、返回地址筛婉、寄存器信息等等。如果程序中沒(méi)有了棧存儲(chǔ)區(qū)癞松,那么程序幾乎無(wú)法運(yùn)行爽撒。
2. 棧的概念(與數(shù)據(jù)結(jié)果中的棧類比)
棧:一種后進(jìn)先出的行為。
3. 棧在函數(shù)調(diào)用時(shí)的作用
問(wèn)題: 為什么說(shuō)函數(shù)調(diào)用時(shí)少不了棧响蓉?
因?yàn)楹瘮?shù)調(diào)用時(shí)硕勿,在內(nèi)存中需要維護(hù)一個(gè)活動(dòng)記錄,活動(dòng)記錄中包含了函數(shù)調(diào)用的參數(shù)枫甲,函數(shù)調(diào)用的返回地址源武,寄存器信息,局部變量想幻,其他數(shù)據(jù)信息(如臨時(shí)變量)等等粱栖,C語(yǔ)言中通過(guò)棧來(lái)維護(hù)活動(dòng)記錄中的信息。
4. 函數(shù)調(diào)用的過(guò)程
總結(jié):每次函數(shù)調(diào)用都會(huì)對(duì)應(yīng)著在棧上建立一個(gè)新的活動(dòng)記錄脏毯,調(diào)用函數(shù)的活動(dòng)記錄位于棧的中部查排,被調(diào)函數(shù)的活動(dòng)記錄位于棧的頂部。
esp:棧頂指針
ebp:函數(shù)調(diào)用結(jié)束的返回地址
5. 函數(shù)調(diào)用的棧變化
-
從main()開(kāi)始運(yùn)行抄沮,在棧中就會(huì)有main()中的活動(dòng)記錄跋核;
main()運(yùn)行后棧中的狀態(tài) -
main()調(diào)用f(),在棧中建立f()的活動(dòng)記錄叛买;
f()運(yùn)行后棧中的狀態(tài) - 當(dāng)從f()調(diào)用中返回到main()
當(dāng)f()運(yùn)行完后返回到main()后棧中的狀態(tài)
椛按空間的數(shù)據(jù)不會(huì)因?yàn)楹瘮?shù)的返回而立即的改變,它只修改了esp指針和ebp指針中的地址值率挣,并沒(méi)有去改變椏桃粒空間中的數(shù)據(jù)。
問(wèn)題:為什么不可以返回函數(shù)內(nèi)部變量的地址(局部變量地址)和局部數(shù)組椒功?
因?yàn)殡m然棧內(nèi)存中的數(shù)據(jù)不會(huì)因?yàn)楹瘮?shù)的返回而改變捶箱,但是如果函數(shù)返回之后,又立即調(diào)用了另一個(gè)函數(shù)动漾,原來(lái)函數(shù)的內(nèi)存空間就會(huì)給另一個(gè)函數(shù)使用丁屎,這樣棧內(nèi)存中的原來(lái)函數(shù)的內(nèi)存空間就會(huì)發(fā)生變化。如果返回局部變量地址或局部數(shù)組是沒(méi)有意義的旱眯,甚至是錯(cuò)誤的晨川,因?yàn)槲覀兊闹羔標(biāo)赶虻木植孔兞炕蚓植繑?shù)組不存在了证九,這樣就會(huì)造成野指針的錯(cuò)誤而使得程序運(yùn)行崩潰。
6. 函數(shù)調(diào)用棧上的數(shù)據(jù)
- 函數(shù)調(diào)用時(shí)共虑,對(duì)應(yīng)的椑⒘空間在函數(shù)返回前是專用的;
- 函數(shù)調(diào)用結(jié)束后妈拌,椨堤常空間將被釋放,數(shù)據(jù)不再有效尘分。
程序說(shuō)明:指向棧數(shù)據(jù)的指針渴逻,函數(shù)返回后,棧中的數(shù)據(jù)沒(méi)有改變
#include <stdio.h>
int* g()
{
int a[10] = {0};
return a;
}
void f()
{
int i = 0;
int b[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int* pointer = g();
for(i=0; i<10; i++)
{
b[i] = pointer[i];
}
for(i=0; i<10; i++)
{
printf("%d\n", b[i]);
}
}
void main()
{
f();
return 0;
}
輸出結(jié)果:
0
0
0
0
0
0
0
0
0
0
程序說(shuō)明:指向棧數(shù)據(jù)的指針音诫,函數(shù)返回后惨奕,局部變量的數(shù)組不存在,會(huì)造成野指針的錯(cuò)誤
#include <stdio.h>
int* g()
{
int a[10] = {0};
return a;
}
void f()
{
int i = 0;
int b[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int* pointer = g();
for(i=0; i<10; i++)
{
printf("%d\n", pointer[i]);
}
}
void main()
{
f();
return 0;
}
輸出結(jié)果:
0
-1217224704
0
0
-1074600104
-1218653521
-1217221952
134514048
-1074600172
-1218653568
總結(jié):活動(dòng)記錄銷毀后竭钝,原先活動(dòng)記錄里面的變量也就被銷毀梨撞,因此局部變量的地址也就沒(méi)有意義了,所以會(huì)出現(xiàn)野指針香罐。
7. 程序中的堆
問(wèn)題:如果說(shuō)在程序中需要一段額外的空間來(lái)完成任務(wù)卧波,比如數(shù)據(jù)結(jié)構(gòu)中經(jīng)常為了效率,用空間換時(shí)間庇茫,這個(gè)時(shí)候我們臨時(shí)的需要一段空間港粱,該怎么辦?——?jiǎng)討B(tài)內(nèi)存分配旦签。
動(dòng)態(tài)內(nèi)存分配是分配堆中的內(nèi)存查坪。
- 堆是程序中一塊預(yù)留的內(nèi)存空間,為了是給程序自由使用宁炫。如我們臨時(shí)需要一段內(nèi)存空間偿曙,可以使用malloc來(lái)在堆內(nèi)存中取一塊來(lái)使用。
- 堆中的內(nèi)存需要主動(dòng)的返還羔巢,因此堆中被程序申請(qǐng)使用的內(nèi)存在被主動(dòng)釋放前將一直有效望忆。
- 如果只申請(qǐng)使用堆空間而不返還,會(huì)導(dǎo)致堆空間使用完畢竿秆, 程序會(huì)越運(yùn)行越慢启摄,最后導(dǎo)致程序無(wú)法運(yùn)行。
問(wèn)題: 棧的作用是什么幽钢?為什么有了棧還需要堆空間歉备?
棧的作用是為了函數(shù)調(diào)用。
在程序運(yùn)行過(guò)程中搅吁,需要臨時(shí)的一段內(nèi)存空間威创,因此需要堆空間來(lái)獲取一段臨時(shí)的內(nèi)存空間。
- C語(yǔ)言程序中通過(guò)庫(kù)函數(shù)的調(diào)用獲得堆空間
頭文件:malloc.h
malloc:以字節(jié)的方式動(dòng)態(tài)申請(qǐng)堆空間
free:將堆空間歸還給系統(tǒng)
系統(tǒng)對(duì)堆空間的管理方式——空閑鏈表法谎懦,位圖法肚豺,對(duì)象池法等等。
空閑鏈表法:
C語(yǔ)言是以高效而聞名的界拦,因此malloc調(diào)用函數(shù)向堆內(nèi)存申請(qǐng)空間時(shí)也必須是高效的吸申。系統(tǒng)將堆中的內(nèi)存組織成一個(gè)鏈表,如上圖所示享甸,圖中每個(gè)節(jié)點(diǎn)的數(shù)字為對(duì)應(yīng)節(jié)點(diǎn)之下的每個(gè)單元的內(nèi)存大小是多少截碴。如12Bytes表示其下方內(nèi)存每個(gè)單元的大小為12Byte。當(dāng)程序調(diào)用malloc函數(shù)之后蛉威,系統(tǒng)就會(huì)遍歷這個(gè)空閑鏈表日丹,查找malloc所需要的內(nèi)存大小跟哪一個(gè)節(jié)點(diǎn)數(shù)的內(nèi)存大小最接近。如上圖中蚯嫌,申請(qǐng)int類型哲虾,即4個(gè)字節(jié)的大小,遍歷空閑鏈表后發(fā)現(xiàn)跟5Bytes節(jié)點(diǎn)最為接近择示,于是將會(huì)在其節(jié)點(diǎn)下方的內(nèi)存中尋找一個(gè)可用的單元束凑,找到后將單元地址返回給p指針。所以說(shuō)栅盲,malloc這個(gè)函數(shù)返回的可用空間可能會(huì)比申請(qǐng)的空間大汪诉,是因?yàn)橄到y(tǒng)通過(guò)空閑鏈表管理堆內(nèi)存時(shí),它會(huì)找malloc申請(qǐng)的最接近的那一個(gè)節(jié)點(diǎn)下對(duì)應(yīng)的堆內(nèi)存谈秫。
8.程序中的靜態(tài)存儲(chǔ)區(qū)
靜態(tài)存儲(chǔ)區(qū)隨著程序的運(yùn)行而分配扒寄,隨著程序的結(jié)束而結(jié)束。也就是說(shuō)靜態(tài)存儲(chǔ)區(qū)在程序運(yùn)行的那一刻就被系統(tǒng)給分配出來(lái)了拟烫,因此旗们,程序在編譯期的時(shí)候就已經(jīng)知道靜態(tài)存儲(chǔ)區(qū)的大小,運(yùn)行的時(shí)候僅僅給靜態(tài)存儲(chǔ)區(qū)分配空間构灸,且在運(yùn)行期靜態(tài)存儲(chǔ)區(qū)的大小是不能改變的上渴。靜態(tài)存儲(chǔ)區(qū)主要是用于保存全局變量和靜態(tài)局部變量。靜態(tài)存儲(chǔ)區(qū)的信息最終會(huì)保存在可執(zhí)行程序中喜颁,即靜態(tài)存儲(chǔ)區(qū)的大小信息稠氮、開(kāi)始信息、結(jié)束信息等保存在.exe或.out可執(zhí)行文件中半开。
靜態(tài)存儲(chǔ)區(qū)的驗(yàn)證
#include <stdio.h>
int g_v = 1;
static int g_vs = 2;
void f()
{
static int g_vl = 3;
printf("&g_vl = %p\n", &g_vl);
}
int main()
{
printf("&g_v = %p\n", &g_v);
printf("&g_vs = %p\n", &g_vs);
f();
return 0;
}
輸出結(jié)果:
&g_v = 0x804a020
&g_vs = 0x804a024
&g_vl = 0x804a028
總結(jié):在內(nèi)存空間中隔披,g_v為全局變量,g_vl為靜態(tài)全局變量寂拆,g_vs為靜態(tài)局部變量奢米,這三個(gè)不同的變量存放在一段連續(xù)的存儲(chǔ)空間中(順序存放)抓韩,因?yàn)檫@個(gè)三個(gè)變量都是存放在靜態(tài)存儲(chǔ)區(qū)的。
9.小結(jié)
- 棧鬓长、堆和靜態(tài)存儲(chǔ)區(qū)是程序中的三個(gè)基本數(shù)據(jù)區(qū)
- 棧的主要作用是為了調(diào)用函數(shù)的時(shí)候維護(hù)對(duì)應(yīng)的活動(dòng)記錄谒拴;
- 堆主要是用于內(nèi)存的動(dòng)態(tài)申請(qǐng)和歸還;
- 靜態(tài)存儲(chǔ)區(qū)用于保存全局變量和靜態(tài)變量涉波。