14:內(nèi)存管理
14.1:內(nèi)存分類與尋址
14.1.1:內(nèi)存分類
在C程序中,能存放數(shù)據(jù)的地方包括:
- 靜態(tài)區(qū):存儲全局變量却特,靜態(tài)局部變量以及常量字符串扶供;常量字符串存儲在.rdata;初始化的全部變量核偿、初始化的全局靜態(tài)變量及初始化的靜態(tài)局部變量存儲在.data诚欠;未初始化的全部變量顽染、未始化的全局靜態(tài)變量及未初始化的全局靜態(tài)變量存放在.bss
- 棧:存儲函數(shù)內(nèi)的局部變量
- 堆:存儲通過malloc()函數(shù)分配的變量
代碼區(qū)(code):.text 代碼段漾岳,這個存放代碼的尼荆,用匯編角度來看就是指令唧垦。
14.1.2:堆棧區(qū)別
棧 | 堆 | |
---|---|---|
內(nèi)存分配 | 由系統(tǒng)自動分配與回收,地址增長方向由高到底 | malloc分配鞭莽,free釋放麸祷,地址增長方向由低到高 |
大小限制 | Windows在應(yīng)用層的棧大小為1M阶牍,而Linux在應(yīng)用層的大小為10M;內(nèi)核層12K到24K不等惧辈,所以在內(nèi)核層開發(fā)時候不能使用遞歸 | 受限于計算機系統(tǒng)中有效的虛擬內(nèi)存 |
執(zhí)行效率 | 系統(tǒng)自動分配盒齿,速度快困食,開發(fā)者無法控制 | 速度比較慢,平凡分配回收倒彰,容易產(chǎn)生內(nèi)存碎片(開發(fā)者可以通過程序減少內(nèi)存碎片的產(chǎn)生) |
存放內(nèi)容 | 記錄程序執(zhí)行中函數(shù)調(diào)用過程中的活動(棧幀)待讳,存放參數(shù)仰剿、返回地址(eip)、ebp琳彩、局部變量等 | 一般是在堆頭部用一個字節(jié)存放堆大小部凑,剩余部分存儲的內(nèi)容由開發(fā)者根據(jù)程序計算的需要決定 |
14.1.3:內(nèi)存地址分類和尋址模式
程序的內(nèi)存尋址是一個重要的概念涂邀。
內(nèi)存地址可以分為邏輯地址,線性地址和物理地址
內(nèi)存地址分類 | 定義 | 組成轉(zhuǎn)化 |
---|---|---|
邏輯地址 | 是編譯器生成的劳较,使用C語言指針時,指針的值就是邏輯地址(及printf打印的地址即邏輯地址)臊恋;對于每個進程他們有一樣的進程地址空間墓捻,類似的邏輯地址,甚至可能相同 | 有段地址+段內(nèi)偏移主城 |
線性地址 | 由分段機制將邏輯地址轉(zhuǎn)化而來岸售,如果沒有分段機制作用凸丸,那么程序的邏輯地址就是線性地址了袱院。 | |
物理地址 | CPU在地址總線上發(fā)出的電平信號,要得到物理地址腻惠,必須要將邏輯地址經(jīng)過分段集灌,分頁等機制轉(zhuǎn)化而來复哆。 |
從邏輯地址到物理地址的翻譯過程叫尋址。x86體系結(jié)構(gòu)下唆阿,使用最多的內(nèi)存尋址模型主要有2種:
- 實模式分段模型(real mode segment model)
- 保護模式平坦模型(protected mode flat model)
介紹內(nèi)存尋址模型首先要了解內(nèi)存的尋址模式
內(nèi)存尋址模型 | 定義 |
---|---|
分段模型 | 在早期16位系統(tǒng)中驯鳖,地址總線占20位支持220=1M的尋址空間久免,16位的寄存器卻只能表示216=64KB的空間,于是8086CPU將1MB的存儲空間分成許多邏輯段摔握,每個段最大限制為64KB(為了能讓16位寄存器尋址丁寄,220=216*2^4=16*64K),段地址()就是邏輯段在主存中的起始位置盛正。每個段地址其實還是20位屑埋,為了能用16位寄存器表示段地址摘能,8086規(guī)定段地址必須是模16地址,即為xxxx0H形式严望,省略低4位0逻恐,段地址就可以用16位數(shù)據(jù)表示,它通常被保存在16位的段寄存器中拨匆。存單元距離段起始位置的偏移量簡稱偏移地址惭每,由于限定每段不超過64KB亏栈,所以偏移地址也可以用16位數(shù)據(jù)表示。 物理地址:在1M字節(jié)的存儲器里览爵,每一個存儲單元都有一個唯一的20位地址镇饮,稱為該存儲單元的物理地址,把段地址左移4位(因為段地址低4位都是零)再加上偏移地址就形成物理地址俱济。 Seg<<4+Offset(左移4位相當(dāng)于2^4) 對于 8086/8088 運行在實模式的程序蛛碌,其實就是運行在實模式分段模型中辖源。對于不同的程序希太,有不同的CS誊辉,DS值亡脑,每個程序的段起始地址都不同霉咨。對于這樣的程序而言,偏移地址16位的特性決定了每個段只有64KB大小惊来。 (這緊緊是在16位系統(tǒng)中) |
扁平模型 | 在32/64位系統(tǒng)棺滞,地址總線和寄存器都是32/64位,這種情況下一個寄存器和指令可以尋址整個線性地址空間枉证。不需要使用基地址室谚,不需要切換CS崔泵,DS,基址可以設(shè)為一個統(tǒng)一的值入篮。 Linux幌甘, Window XP/7采用的內(nèi)存尋址模型锅风,Linux中,段主要分為4種肮帐,即為內(nèi)核代碼段边器,內(nèi)核數(shù)據(jù)段托修,用戶代碼段诀黍,用戶數(shù)據(jù)段仗处。對于內(nèi)核代碼段和數(shù)據(jù)段而言婆誓,CS,DS的值是0xC00000000也颤,而用戶代碼和數(shù)據(jù)段的CS,DS的值是0x00000000。 |
實模式 | 實模式運行于20位地址總線文留,尋址空間1M燥翅,16位的寄存器無法表示蜕提,因此段被分成64KB,1M分為16個64KB凛膏,CS脏榆、DS等存儲的是段的起始地址通過CS<<4+IP進行尋址(一個基址寄存器+一個段寄存器聯(lián)合起來則可以表示更大的一個地址空間须喂。于是發(fā)明了這種段寄存器左移4位+基址寄存器用以間接尋址。) |
保護模式 | 啟用了32位總線胯府,地址使用的是虛擬地址恨胚,引入了頁表和段描述符表,用GDT和LDT段描述符表的數(shù)據(jù)結(jié)構(gòu)來定義每個段寒波,通過頁表等進行尋址 |
保護模式虛擬地址到物理地址尋址
邏輯地址=》線性地址(32位或者64位系統(tǒng)(64位太大,一般用44绸栅、48位))==》物理地址
如:x64中使用低48位页屠,每級頁表占9位,共4級风纠,縮寫分為PML4竹观,PDP潜索,PD,PT
x86二級尋址
x64四級尋址
14.2:內(nèi)存分配
內(nèi)存分配,分為兩種:
- 在棧上分配內(nèi)存由驹;
- 在堆上分配;
14.2.1:棧上分配
在函數(shù)內(nèi)部定義局部變量并炮,就是在棧上分配內(nèi)存甥郑。占內(nèi)存大小是有限制的澜搅,不能超過棧的大小伍俘,否則會造成棧的溢出。棧上分配的內(nèi)存系統(tǒng)會在函數(shù)運行結(jié)束后自動回收癌瘾。
void func() {
int Number[1024]={0};//Number數(shù)組從棧上分配內(nèi)存
}
Windows在應(yīng)用層的棧大小為1M饵溅,而Linux在應(yīng)用層的大小為10M。
14.2.2:堆上分配
堆上的內(nèi)存:malloc()函數(shù)分配的內(nèi)存冠句;free()函數(shù)釋放內(nèi)存;系統(tǒng)不會自動回收懦底,需開發(fā)者手動釋放罕扎;free之后一定置NULL。
int *p=(int *)malloc(10*sizeof(int));
free(p);
//當(dāng)調(diào)用free()把p所指堆上的內(nèi)存釋放(回收)之后拱层,p指向了一個無效內(nèi)存地址宴咧,成為了野指針掺栅,所以需要重新將p置為NULL纳猪,如下:
p=NULL;
14.2.2.1:malloc/calloc/realloc
void *malloc(unsigned int num_bytes);
void *calloc(size_t n, size_t size);//元素格式,長度
void *realloc(void *mem_address, unsigned int newsize);
/*
malloc:分配內(nèi)存沙绝,空間不初始化為零鼠锈;失敗返回NULL,成功返回內(nèi)存地址粗悯,內(nèi)存中位垃圾值同欠,需要清零free
calloc:在內(nèi)存的動態(tài)存儲區(qū)中分配n個長度為size的連續(xù)空間铺遂,函數(shù)返回一個指向分配起始地址的指針,內(nèi)存空間初始化為0.
realloc:先判斷當(dāng)前的指針是否有足夠的連續(xù)空間撤逢,如果有,擴大mem_address指向的地址泉沾,并且將mem_address返回妇押,如果空間不夠,先按照newsize指定的大小分配空間俊马,將原有數(shù)據(jù)從頭到尾拷貝到新分配的內(nèi)存區(qū)域梆掸,而后釋放原來mem_address所指內(nèi)存區(qū)域(注意:原來指針是自動釋放,不需要使用free),同時返回新分配的內(nèi)存區(qū)域的首地址夫偶。即重新分配存儲器塊的地址界睁。
14.2.3:靜態(tài)區(qū)分配
靜態(tài)區(qū)分配:全局變量或者靜態(tài)局部變量的內(nèi)存空間
//定義如下的全局變量:
int g_iNumber[1024]={0};
//則g_iNumber就是在靜態(tài)區(qū)獲取內(nèi)存。靜態(tài)區(qū)獲取的內(nèi)存不需要自己釋放翻斟,系統(tǒng)會在程序退出的時候自動回收说铃。
14.3:內(nèi)存泄露與檢測
內(nèi)存泄露:從堆上分配內(nèi)存,如果不注意釋放债热,就會產(chǎn)生內(nèi)存泄漏
- malloc/free;new/delete要配對出現(xiàn)衙解;寫完分配馬上寫釋放。
- goto語句防止內(nèi)存泄露
- 誰分配誰釋放舌剂,不要分離
goto語句防止內(nèi)存泄露
NTSTATUS QueryObjectName(HANDLE h) {
int ret;
NTSTATUS st;
char *str = "Hello, how are u?";
char *pstr = (char *)malloc(256);
if (pstr == NULL) {
printf("No more memory\n");
goto Error;
}
strncpy(pstr, str, strlen(str));
pstr[strlen(str)] = '\0';
char *namebuf = (char *)malloc(1024);
if (buf == NULL) {
printf("No more memory\n");
goto Error;
}
st = NtQueryObject(h, FileNameInformation, namebuf, ret, NULL);
if (!NT_SUCCESS(st)) {
printf("No more memory\n");
goto Error;
}
Error:
//發(fā)生錯誤后霍转,統(tǒng)一處理內(nèi)存釋放問題
if (buf) {
free(buf);
}
if (pstr) {
free(pstr);
}
return st;
}
誰分配誰釋放避消,不要分離
char *func() {
char *p = malloc(128);
return p
}
void func1() {
char *p = malloc(128);
free(p);
p = NULL;
return;
}