日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

C语言内存管理超详解

發(fā)布時(shí)間:2025/6/15 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 C语言内存管理超详解 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

補(bǔ)充:

1.一個(gè)正在運(yùn)行著的C編譯程序占用的內(nèi)存分為棧區(qū)、堆區(qū)、未初始化數(shù)據(jù)區(qū)(BBS)、初始化數(shù)據(jù)區(qū)、代碼區(qū)5個(gè)部分。
(1)棧區(qū):存放函數(shù)的參數(shù)值、局部變量的值。由編譯器自動(dòng)分配釋放。
(2)堆區(qū):用于動(dòng)態(tài)內(nèi)存分配。由用戶通過(guò)malloc或new函數(shù)分配,由用戶通過(guò)free或delete函數(shù)釋放。
(3)未初始化數(shù)據(jù)區(qū)(BBS):存放未初始化的全局變量。
(4)初始化數(shù)據(jù)區(qū):存放已初始化的全局變量、靜態(tài)變量和常量。
(5)代碼區(qū):存放程序的執(zhí)行代碼。

注意:(內(nèi)存的分配方式也可是一下的)

1.靜態(tài)存儲(chǔ)區(qū):存全局變量和static變量。在程序編譯的時(shí)候就已經(jīng)分配好,這塊內(nèi)存在程序的整個(gè)運(yùn)行期間。

2.棧:

3.堆:

?

注意:

(1)不要使用未分配成功的內(nèi)存
在我們申請(qǐng)內(nèi)存的時(shí)候可能出現(xiàn)內(nèi)存分配不成功。使用之前先去檢查指針是否為空。如果是動(dòng)態(tài)分配的內(nèi)存,那么在使用之前先用if(p == NULL )判斷。
如果是作為函數(shù)的參數(shù),使用assert(p != NULL)檢查。

(2)對(duì)分配成功的內(nèi)存要初始化
對(duì)于一個(gè)變量來(lái)說(shuō),如果沒有初始化,系統(tǒng)給變量一個(gè)什么樣的值是不確定的,這樣就會(huì)出現(xiàn)因?yàn)樽兞砍跏贾刀l(fā)一些問(wèn)題。所以,給申請(qǐng)的變量賦初值是很有必要的。

(3)內(nèi)存越界
最多體現(xiàn)在數(shù)組越界問(wèn)題。

(4)內(nèi)存泄漏
需要自己去釋放的內(nèi)存只有動(dòng)態(tài)分配的堆區(qū)的內(nèi)存,內(nèi)存申請(qǐng)和釋放必須配對(duì)。只申請(qǐng),不釋放,內(nèi)存一直被占用,這樣總會(huì)有內(nèi)存不夠的時(shí)候。

(5)繼續(xù)使用已釋放的內(nèi)存
動(dòng)態(tài)申請(qǐng)的內(nèi)存被釋放后,還繼續(xù)使用的。

?

文章出處:https://www.cnblogs.com/tuhooo/p/7221136.html

深入理解C語(yǔ)言內(nèi)存管理

之前在學(xué)Java的時(shí)候?qū)τ贘ava虛擬機(jī)中的內(nèi)存分布有一定的了解,但是最近在看一些C,發(fā)現(xiàn)居然自己對(duì)于C語(yǔ)言的內(nèi)存分配了解的太少。

問(wèn)題不能拖,我這就來(lái)學(xué)習(xí)一下吧,爭(zhēng)取一次搞定。?在任何程序設(shè)計(jì)環(huán)境及語(yǔ)言中,內(nèi)存管理都十分重要。

內(nèi)存管理的基本概念

分析C語(yǔ)言內(nèi)存的分布先從Linux下可執(zhí)行的C程序入手。現(xiàn)在有一個(gè)簡(jiǎn)單的C源程序hello.c

1 #include <stdio.h> 2 #include <stdlib.h> 3 int var1 = 1; 4 5 int main(void) { 6 int var2 = 2; 7 printf("hello, world!\n"); 8 exit(0); 9 }

經(jīng)過(guò)gcc hello.c進(jìn)行編譯之后得到了名為a.out的可執(zhí)行文件

[tuhooo@localhost leet_code]$?ls -al a.out
-rwxrwxr-x. 1 tuhooo tuhooo 8592 Jul 22 20:40 a.out

ls命令是查看文件的元數(shù)據(jù)信息

[tuhooo@localhost leet_code]$?file a.out
a.out: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=23c58f2cad39d8b15b91f0cc8129055833372afe, not stripped

file命令用來(lái)識(shí)別文件類型,也可用來(lái)辨別一些文件的編碼格式。

它是通過(guò)查看文件的頭部信息來(lái)獲取文件類型,而不是像Windows通過(guò)擴(kuò)展名來(lái)確定文件類型的。

[tuhooo@localhost leet_code]$?size a.out

text?databssdechexfilename
(代碼區(qū)靜態(tài)數(shù)據(jù))(全局初始化靜態(tài)數(shù)據(jù))(未初始化數(shù)據(jù)區(qū))?(十進(jìn)制總和)(十六制總和)(文件名)
13015608186974da.out

顯示一個(gè)目標(biāo)文件或者鏈接庫(kù)文件中的目標(biāo)文件的各個(gè)段的大小,當(dāng)沒有輸入文件名時(shí),默認(rèn)為a.out。

size:支持的目標(biāo): elf32-i386 a.out-i386-linux efi-app-ia32 elf32-little elf32-big srec symbolsrec tekhex binary ihex trad-core。

那啥,可執(zhí)行文件在存儲(chǔ)(也就是還沒有載入到內(nèi)存中)的時(shí)候,分為:代碼區(qū)數(shù)據(jù)區(qū)未初始化數(shù)據(jù)區(qū)3個(gè)部分。

進(jìn)一步解讀

(1)代碼區(qū)(text segment)。存放CPU執(zhí)行的機(jī)器指令(machine instructions)。通常,代碼區(qū)是可共享的(即另外的執(zhí)行程序可以調(diào)用它),因?yàn)閷?duì)于頻繁被執(zhí)行的程序,只需要在內(nèi)存中有一份代碼即可。代碼區(qū)通常是只讀的,使其只讀的原因是防止程序意外地修改了它的指令。另外,代碼區(qū)還規(guī)劃了局部變量的相關(guān)信息。

(2)全局初始化數(shù)據(jù)區(qū)/靜態(tài)數(shù)據(jù)區(qū)(initialized data segment/data segment)。該區(qū)包含了在程序中明確被初始化的全局變量、靜態(tài)變量(包括全局靜態(tài)變量和局部靜態(tài)變量)和常量數(shù)據(jù)(如字符串常量)。例如,一個(gè)不在任何函數(shù)內(nèi)的聲明(全局?jǐn)?shù)據(jù)):

1 int maxcount = 99;

使得變量maxcount根據(jù)其初始值被存儲(chǔ)到初始化數(shù)據(jù)區(qū)中。

1 static mincount = 100;?

這聲明了一個(gè)靜態(tài)數(shù)據(jù),如果是在任何函數(shù)體外聲明,則表示其為一個(gè)全局靜態(tài)變量,如果在函數(shù)體內(nèi)(局部),則表示其為一個(gè)局部靜態(tài)變量。另外,如果在函數(shù)名前加上static,則表示此函數(shù)只能在當(dāng)前文件中被調(diào)用。

(3)未初始化數(shù)據(jù)區(qū)。亦稱BSS區(qū)(uninitialized data segment),存入的是全局未初始化變量。BSS這個(gè)叫法是根據(jù)一個(gè)早期的匯編運(yùn)算符而來(lái),這個(gè)匯編運(yùn)算符標(biāo)志著一個(gè)塊的開始。BSS區(qū)的數(shù)據(jù)在程序開始執(zhí)行之前被內(nèi)核初始化為0或者空指針(NULL)。例如一個(gè)不在任何函數(shù)內(nèi)的聲明:

1 long sum[1000];

將變量sum存儲(chǔ)到未初始化數(shù)據(jù)區(qū)。

下圖所示為可執(zhí)行代碼存儲(chǔ)時(shí)結(jié)構(gòu)和運(yùn)行時(shí)結(jié)構(gòu)的對(duì)照?qǐng)D。一個(gè)正在運(yùn)行著的C編譯程序占用的內(nèi)存分為代碼區(qū)、初始化數(shù)據(jù)區(qū)、未初始化數(shù)據(jù)區(qū)、堆區(qū)和棧區(qū)5個(gè)部分。

?

再來(lái)看一張圖,多個(gè)一個(gè)命令行參數(shù)區(qū):

(1)代碼區(qū)(text segment)。代碼區(qū)指令根據(jù)程序設(shè)計(jì)流程依次執(zhí)行,對(duì)于順序指令,則只會(huì)執(zhí)行一次(每個(gè)進(jìn)程),如果反復(fù),則需要使用跳轉(zhuǎn)指令,如果進(jìn)行遞歸,則需要借助棧來(lái)實(shí)現(xiàn)。代碼段: 代碼段(code segment/text segment )通常是指用來(lái)存放程序執(zhí)行代碼的一塊內(nèi)存區(qū)域。這部分區(qū)域的大小在程序運(yùn)行前就已經(jīng)確定,并且內(nèi)存區(qū)域通常屬于只讀, 某些架構(gòu)也允許代碼段為可寫,即允許修改程序。在代碼段中,也有可能包含一些只讀的常數(shù)變量,例如字符串常量等。代碼區(qū)的指令中包括操作碼和要操作的對(duì)象(或?qū)ο蟮刂芬?#xff09;。如果是立即數(shù)(即具體的數(shù)值,如5),將直接包含在代碼中;如果是局部數(shù)據(jù),將在棧區(qū)分配空間,然后引用該數(shù)據(jù)地址;如果是BSS區(qū)和數(shù)據(jù)區(qū),在代碼中同樣將引用該數(shù)據(jù)地址。另外,代碼段還規(guī)劃了局部數(shù)據(jù)所申請(qǐng)的內(nèi)存空間信息。

(2)全局初始化數(shù)據(jù)區(qū)/靜態(tài)數(shù)據(jù)區(qū)(Data Segment)。只初始化一次。數(shù)據(jù)段: 數(shù)據(jù)段(data segment )通常是指用來(lái)存放程序中已初始化的全局變量的一塊內(nèi)存區(qū)域。數(shù)據(jù)段屬于靜態(tài)內(nèi)存分配。data段中的靜態(tài)數(shù)據(jù)區(qū)存放的是程序中已初始化的全局變量、靜態(tài)變量和常量。

(3)未初始化數(shù)據(jù)區(qū)(BSS)。在運(yùn)行時(shí)改變其值。BSS 段: BSS 段(bss segment )通常是指用來(lái)存放程序中未初始化的全局變量的一塊內(nèi)存區(qū)域。BSS 是英文Block Started by Symbol 的簡(jiǎn)稱。BSS 段屬于靜態(tài)內(nèi)存分配,即程序一開始就將其清零了。一般在初始化時(shí)BSS段部分將會(huì)清零。

(4)棧區(qū)(stack)。由編譯器自動(dòng)分配釋放,存放函數(shù)的參數(shù)值、局部變量的值等。存放函數(shù)的參數(shù)值、局部變量的值,以及在進(jìn)行任務(wù)切換時(shí)存放當(dāng)前任務(wù)的上下文內(nèi)容。其操作方式類似于數(shù)據(jù)結(jié)構(gòu)中的棧。每當(dāng)一個(gè)函數(shù)被調(diào)用,該函數(shù)返回地址和一些關(guān)于調(diào)用的信息,比如某些寄存器的內(nèi)容,被存儲(chǔ)到棧區(qū)。然后這個(gè)被調(diào)用的函數(shù)再為它的自動(dòng)變量和臨時(shí)變量在棧區(qū)上分配空間,這就是C實(shí)現(xiàn)函數(shù)遞歸調(diào)用的方法。每執(zhí)行一次遞歸函數(shù)調(diào)用,一個(gè)新的棧框架就會(huì)被使用,這樣這個(gè)新實(shí)例棧里的變量就不會(huì)和該函數(shù)的另一個(gè)實(shí)例棧里面的變量混淆。棧(stack)?:棧又稱堆棧, 是用戶存放程序臨時(shí)創(chuàng)建的局部變量,也就是說(shuō)我們函數(shù)括弧"{}"中定義的變量(但不包括static 聲明的變量,static 意味著在數(shù)據(jù)段中存放變量)。除此以外,在函數(shù)被調(diào)用時(shí),其參數(shù)也會(huì)被壓入發(fā)起調(diào)用的進(jìn)程棧中,并且待到調(diào)用結(jié)束后,函數(shù)的返回值也會(huì)被存放回棧中。由于棧的先進(jìn)先出特點(diǎn),所以棧特別方便用來(lái)保存/ 恢復(fù)調(diào)用現(xiàn)場(chǎng)。從這個(gè)意義上講,我們可以把堆棧看成一個(gè)寄存、交換臨時(shí)數(shù)據(jù)的內(nèi)存區(qū)。

(5)堆區(qū)(heap)。用于動(dòng)態(tài)內(nèi)存分配。堆在內(nèi)存中位于bss區(qū)和棧區(qū)之間。一般由程序員分配和釋放,若程序員不釋放,程序結(jié)束時(shí)有可能由OS回收。堆(heap): 堆是用于存放進(jìn)程運(yùn)行中被動(dòng)態(tài)分配的內(nèi)存段,它的大小并不固定,可動(dòng)態(tài)擴(kuò)張或縮減。當(dāng)進(jìn)程調(diào)用malloc 等函數(shù)分配內(nèi)存時(shí),新分配的內(nèi)存就被動(dòng)態(tài)添加到堆上(堆被擴(kuò)張);當(dāng)利用free 等函數(shù)釋放內(nèi)存時(shí),被釋放的內(nèi)存從堆中被剔除(堆被縮減)。在將應(yīng)用程序加載到內(nèi)存空間執(zhí)行時(shí),操作系統(tǒng)負(fù)責(zé)代碼段、數(shù)據(jù)段和BSS段的加載,并將在內(nèi)存中為這些段分配空間。棧段亦由操作系統(tǒng)分配和管理,而不需要程序員顯示地管理;堆段由程序員自己管理,即顯式地申請(qǐng)和釋放空間。

另外,可執(zhí)行程序在運(yùn)行時(shí)具有相應(yīng)的程序?qū)傩浴T谟胁僮飨到y(tǒng)支持時(shí),這些屬性頁(yè)由操作系統(tǒng)管理和維護(hù)。

C語(yǔ)言程序編譯完成之后,已初始化的全局變量保存在DATA段中,未初始化的全局變量保存在BSS段中。TEXT和DATA段都在可執(zhí)行文件中,由系統(tǒng)從可執(zhí)行文件中加載;而BSS段不在可執(zhí)行文件中,由系統(tǒng)初始化。BSS段只保存沒有值的變量,所以事實(shí)上它并不需要保存這些變量的映像。運(yùn)行時(shí)所需要的BSS段大小記錄在目標(biāo)文件中,但是BSS段并不占據(jù)目標(biāo)文件的任何空間。

以上兩圖來(lái)自于《C語(yǔ)言專家編程》。

在操作系統(tǒng)中,一個(gè)進(jìn)程就是處于執(zhí)行期的程序(當(dāng)然包括系統(tǒng)資源),實(shí)際上正在執(zhí)行的程序代碼的活標(biāo)本。那么進(jìn)程的邏輯地址空間是如何劃分的呢?

?

左邊的是UNIX/LINUX系統(tǒng)的執(zhí)行文件,右邊是對(duì)應(yīng)進(jìn)程邏輯地址空間的劃分情況。

首先是堆棧區(qū)(stack),堆棧是由編譯器自動(dòng)分配釋放,存放函數(shù)的參數(shù)值,局部變量的值等。其操作方式類似于數(shù)據(jù)結(jié)構(gòu)中的棧。棧的申請(qǐng)是由系統(tǒng)自動(dòng)分配,如在函數(shù)內(nèi)部申請(qǐng)一個(gè)局部變量 int h,同時(shí)判別所申請(qǐng)空間是否小于棧的剩余空間,如若小于的話,在堆棧中為其開辟空間,為程序提供內(nèi)存,否則將報(bào)異常提示棧溢出。??

其次是堆(heap),堆一般由程序員分配釋放, 若程序員不釋放,程序結(jié)束時(shí)可能由OS回收 。注意它與數(shù)據(jù)結(jié)構(gòu)中的堆是兩回事,分配方式倒是類似于鏈表。堆的申請(qǐng)是由程序員自己來(lái)操作的,在C中使用malloc函數(shù),而C++中使用new運(yùn)算符,但是堆的申請(qǐng)過(guò)程比較復(fù)雜:當(dāng)系統(tǒng)收到程序的申請(qǐng)時(shí),會(huì)遍歷記錄空閑內(nèi)存地址的鏈表,以求尋找第一個(gè)空間大于所申請(qǐng)空間的堆結(jié)點(diǎn),然后將該結(jié)點(diǎn)從空閑 結(jié)點(diǎn)鏈表中刪除,并將該結(jié)點(diǎn)的空間分配給程序,此處應(yīng)該注意的是有些情況下,新申請(qǐng)的內(nèi)存塊的首地址記錄本次分配的內(nèi)存塊大小,這樣在delete尤其是 delete[]時(shí)就能正確的釋放內(nèi)存空間。

接著是全局?jǐn)?shù)據(jù)區(qū)(靜態(tài)區(qū)) (static),全局變量和靜態(tài)變量的存儲(chǔ)是放在一塊的初始化的全局變量和靜態(tài)變量在一塊區(qū)域,?未初始化的全局變量和未初始化的靜態(tài)變量在相鄰的另一塊區(qū)域。?另外文字常量區(qū),常量字符串就是放在這里,程序結(jié)束后有系統(tǒng)釋放

最后是程序代碼區(qū),放著函數(shù)體的二進(jìn)制代碼。

為什么要這么分配內(nèi)存?

(1)一個(gè)進(jìn)程在運(yùn)行過(guò)程中,代碼是根據(jù)流程依次執(zhí)行的,只需要訪問(wèn)一次,當(dāng)然跳轉(zhuǎn)和遞歸有可能使代碼執(zhí)行多次,而數(shù)據(jù)一般都需要訪問(wèn)多次,因此單獨(dú)開辟空間以方便訪問(wèn)和節(jié)約空間。

(2)臨時(shí)數(shù)據(jù)及需要再次使用的代碼在運(yùn)行時(shí)放入棧區(qū)中,生命周期短。

(3)全局?jǐn)?shù)據(jù)和靜態(tài)數(shù)據(jù)有可能在整個(gè)程序執(zhí)行過(guò)程中都需要訪問(wèn),因此單獨(dú)存儲(chǔ)管理。

(4)堆區(qū)由用戶自由分配,以便管理。

舉例說(shuō)明內(nèi)存分布情況

1 /* memory_allocate.c用于演示內(nèi)存分布情況 */2 3 int a = 0; /* a在全局已初始化數(shù)據(jù)區(qū) */4 char *p1; /* p1在BSS區(qū)(未初始化全局變量) */5 6 int main(void) {7 int b; /* b在棧區(qū) */8 char s[] = "abc"; /* s為數(shù)組變量, 存儲(chǔ)在棧區(qū) */9 /* "abc"為字符串常量, 存儲(chǔ)在已初始化數(shù)據(jù)區(qū) */ 10 char *p1, p2; /* p1、p2在棧區(qū) */ 11 char *p3 = "123456"; /* "123456\0"已初始化在數(shù)據(jù)區(qū), p3在棧區(qū) */ 12 static int c = 0; /* c為全局(靜態(tài))數(shù)據(jù), 存在于已初始化數(shù)據(jù)區(qū) */ 13 /* 另外, 靜態(tài)數(shù)據(jù)會(huì)自動(dòng)初始化 */ 14 p1 = (char *)malloc(10); /* 分配的10個(gè)字節(jié)的區(qū)域存在于堆區(qū) */ 15 p2 = (char *)malloc(20); /* 分配得來(lái)的20個(gè)字節(jié)的區(qū)域存在于堆區(qū) */ 16 17 free(p1); 18 free(p2); 19 }

內(nèi)存的分配方式

在C語(yǔ)言中,對(duì)象可以使用靜態(tài)或動(dòng)態(tài)的方式分配內(nèi)存空間。

靜態(tài)分配:編譯器在處理程序源代碼時(shí)分配。

動(dòng)態(tài)分配:程序在執(zhí)行時(shí)調(diào)用malloc庫(kù)函數(shù)申請(qǐng)分配。

靜態(tài)內(nèi)存分配是在程序執(zhí)行之前進(jìn)行的因而效率比較高,而動(dòng)態(tài)內(nèi)存分配則可以靈活的處理未知數(shù)目的。

靜態(tài)與動(dòng)態(tài)內(nèi)存分配的主要區(qū)別如下:

靜態(tài)對(duì)象是有名字的變量,可以直接對(duì)其進(jìn)行操作;動(dòng)態(tài)對(duì)象是沒有名字的一段地址,需要通過(guò)指針間接地對(duì)它進(jìn)行操作。

靜態(tài)對(duì)象的分配與釋放由編譯器自動(dòng)處理;動(dòng)態(tài)對(duì)象的分配與釋放必須由程序員顯式地管理,它通過(guò)malloc()和free兩個(gè)函數(shù)來(lái)完成。

以下是采用靜態(tài)分配方式的例子。

1 int a = 100;

此行代碼指示編譯器分配足夠的存儲(chǔ)區(qū)以存放一個(gè)整型值,該存儲(chǔ)區(qū)與名字a相關(guān)聯(lián),并用數(shù)值100初始化該存儲(chǔ)區(qū)。

以下是采用動(dòng)態(tài)分配方式的例子:

1 p1 = (char *)malloc(10*sizeof(int));

此行代碼分配了10個(gè)int類型的對(duì)象,然后返回對(duì)象在內(nèi)存中的地址,接著這個(gè)地址被用來(lái)初始化指針對(duì)象p1,對(duì)于動(dòng)態(tài)分配的內(nèi)存唯一的訪問(wèn)方式是通過(guò)指針間接地訪問(wèn),其釋放方法為:

1 free(p1);

棧和堆的區(qū)別

前面已經(jīng)介紹過(guò),棧是由編譯器在需要時(shí)分配的,不需要時(shí)自動(dòng)清除的變量存儲(chǔ)區(qū)。里面的變量通常是局部變量、函數(shù)參數(shù)等。堆是由malloc()函數(shù)分配的內(nèi)存塊,內(nèi)存釋放由程序員手動(dòng)控制,在C語(yǔ)言為free函數(shù)完成。棧和堆的主要區(qū)別有以下幾點(diǎn):

(1)管理方式不同。

棧編譯器自動(dòng)管理,無(wú)需程序員手工控制;而堆空間的申請(qǐng)釋放工作由程序員控制,容易產(chǎn)生內(nèi)存泄漏。對(duì)于棧來(lái)講,是由編譯器自動(dòng)管理,無(wú)需我們手工控制;對(duì)于堆來(lái)說(shuō),釋放工作由程序員控制,容易產(chǎn)生memory?leak。空間大小:一般來(lái)講在32位系統(tǒng)下,堆內(nèi)存可以達(dá)到4G的空間,從這個(gè)角度來(lái)看堆內(nèi)存幾乎是沒有什么限制的。但是對(duì)于棧來(lái)講,一般都是有一定的空間大小的,例如,在VC6下面,默認(rèn)的棧空間大小是1M。當(dāng)然,這個(gè)值可以修改。碎片問(wèn)題:對(duì)于堆來(lái)講,頻繁的new/delete勢(shì)必會(huì)造成內(nèi)存空間的不連續(xù),從而造成大量的碎片,使程序效率降低。對(duì)于棧來(lái)講,則不會(huì)存在這個(gè)問(wèn)?題,因?yàn)闂J窍冗M(jìn)后出的隊(duì)列,他們是如此的一一對(duì)應(yīng),以至于永遠(yuǎn)都不可能有一個(gè)內(nèi)存塊從棧中間彈出,在它彈出之前,在它上面的后進(jìn)的棧內(nèi)容已經(jīng)被彈出,詳細(xì)的可以參考數(shù)據(jù)結(jié)構(gòu)。生長(zhǎng)方向:對(duì)于堆來(lái)講,生長(zhǎng)方向是向上的,也就是向著內(nèi)存地址增加的方向;對(duì)于棧來(lái)講,它的生長(zhǎng)方向是向下的,是向著內(nèi)存地址減小的方向增長(zhǎng)。分配方式:堆都是動(dòng)態(tài)分配的,沒有靜態(tài)分配的堆。棧有2種分配方式:靜態(tài)分配和動(dòng)態(tài)分配。靜態(tài)分配是編譯器完成的,?比如局部變量的分配。動(dòng)態(tài)分配由malloca函數(shù)進(jìn)行分配,但是棧的動(dòng)態(tài)分配和堆是不同的,它的動(dòng)態(tài)分配是由編譯器進(jìn)行釋放,無(wú)需我們手工實(shí)現(xiàn)。分配效率:棧是機(jī)器系統(tǒng)提供的數(shù)據(jù)結(jié)構(gòu),計(jì)算機(jī)會(huì)在底層對(duì)棧提供支持:分配專門的寄存器存放棧的地址,壓棧出棧都有專門的指令執(zhí)行,這就決定了棧的效率比較高。堆則是C/C++函數(shù)庫(kù)提供的,它的機(jī)制是很復(fù)雜的,例如為了分配一塊內(nèi)存,庫(kù)函數(shù)會(huì)按照一定的算法(具體的算法可以參考數(shù)據(jù)結(jié)構(gòu)/操作系統(tǒng))在堆內(nèi)存中搜索可用的足夠大小的空間,如果沒有足夠大小的空間(可能是由于內(nèi)存碎片太多),就有可能調(diào)用系統(tǒng)功能去增加程序數(shù)據(jù)段的內(nèi)存空間,這樣就有機(jī)會(huì)分到足夠大小的內(nèi)存,然后進(jìn)行返回。顯然,堆的效率比棧要低得多。從這里我們可以看到,堆和棧相比,由于大量new/delete的使用,容易造成大量的內(nèi)存碎片;由于沒有專門的系統(tǒng)支持,效率很低;由于可能引發(fā)用戶態(tài)和核心態(tài)的切換,內(nèi)存的申請(qǐng),代價(jià)變得更加昂貴。所以棧在程序中是應(yīng)用最廣泛的,就算是函數(shù)的調(diào)用也利用棧去完成,函數(shù)調(diào)用過(guò)程中的參數(shù),返回地址,?EBP和局部變量都采用棧的方式存放。所以,我們推薦大家盡量用棧,而不是用堆。雖然棧有如此眾多的好處,但是由于和堆相比不是那么靈活,有時(shí)候分配大量的內(nèi)存空間,還是用堆好一些。無(wú)論是堆還是棧,都要防止越界現(xiàn)象的發(fā)生(除非你是故意使其越界),因?yàn)樵浇绲慕Y(jié)果要么是程序崩潰,要么是摧毀程序的堆、棧結(jié)構(gòu),產(chǎn)生以想不到的結(jié)果。

(2)空間大小不同。

棧是向低地址擴(kuò)展的數(shù)據(jù)結(jié)構(gòu),是一塊連續(xù)的內(nèi)存區(qū)域。這句話的意思是棧頂?shù)牡刂泛蜅5淖畲笕萘渴窍到y(tǒng)預(yù)先規(guī)定好的,當(dāng)申請(qǐng)的空間超過(guò)棧的剩余空間時(shí),將提示溢出。因此,用戶能從棧獲得的空間較小。

堆是向高地址擴(kuò)展的數(shù)據(jù)結(jié)構(gòu),是不連續(xù)的內(nèi)存區(qū)域。因?yàn)橄到y(tǒng)是用鏈表來(lái)存儲(chǔ)空閑內(nèi)存地址的,且鏈表的遍歷方向是由低地址向高地址。由此可見,堆獲得的空間較靈活,也較大。棧中元素都是一一對(duì)應(yīng)的,不會(huì)存在一個(gè)內(nèi)存塊從棧中間彈出的情況。

在Windows下,棧是向低地址擴(kuò)展的數(shù)據(jù)結(jié)構(gòu),是一塊連續(xù)的內(nèi)存的區(qū)域。這句話的意思是棧頂?shù)牡刂泛蜅5淖畲笕萘渴窍到y(tǒng)預(yù)先規(guī)定好的,在 WINDOWS下,棧的大小是2M(也有的說(shuō)是1M,總之是一個(gè)編譯時(shí)就確定的常數(shù)),如果申請(qǐng)的空間超過(guò)棧的剩余空間時(shí),將提示overflow。因此,能從棧獲得的空間較小。堆:堆是向高地址擴(kuò)展的數(shù)據(jù)結(jié)構(gòu),是不連續(xù)的內(nèi)存區(qū)域。這是由于系統(tǒng)是用鏈表來(lái)存儲(chǔ)的空閑內(nèi)存地址的,自然是不連續(xù)的,而鏈表的遍歷方向是由低地址向高地址。堆的大小受限于計(jì)算機(jī)系統(tǒng)中有效的虛擬內(nèi)存。由此可見,堆獲得的空間比較靈活,也比較大。

(3)是否產(chǎn)生碎片。

對(duì)于堆來(lái)講,頻繁的malloc/free(new/delete)勢(shì)必會(huì)造成內(nèi)存空間的不連續(xù),從而造成大量的碎片,使程序效率降低(雖然程序在退出后操作系統(tǒng)會(huì)對(duì)內(nèi)存進(jìn)行回收管理)。對(duì)于棧來(lái)講,則不會(huì)存在這個(gè)問(wèn)題。

(4)增長(zhǎng)方向不同。

堆的增長(zhǎng)方向是向上的,即向著內(nèi)存地址增加的方向;棧的增長(zhǎng)方向是向下的,即向著內(nèi)存地址減小的方向。

(5)分配方式不同。

堆都是程序中由malloc()函數(shù)動(dòng)態(tài)申請(qǐng)分配并由free()函數(shù)釋放的;棧的分配和釋放是由編譯器完成的,棧的動(dòng)態(tài)分配由alloca()函數(shù)完成,但是棧的動(dòng)態(tài)分配和堆是不同的,他的動(dòng)態(tài)分配是由編譯器進(jìn)行申請(qǐng)和釋放的,無(wú)需手工實(shí)現(xiàn)。

STACK: 由系統(tǒng)自動(dòng)分配。例如,聲明在函數(shù)中一個(gè)局部變量 int b;系統(tǒng)自動(dòng)在棧中為b開辟空間。HEAP:需要程序員自己申請(qǐng),并指明大小,在C中malloc函數(shù)。指向堆中分配內(nèi)存的指針則可能是存放在棧中的。

(6)分配效率不同。

棧是機(jī)器系統(tǒng)提供的數(shù)據(jù)結(jié)構(gòu),計(jì)算機(jī)會(huì)在底層對(duì)棧提供支持:分配專門的寄存器存放棧的地址,壓棧出棧都有專門的指令執(zhí)行。堆則是C函數(shù)庫(kù)提供的,它的機(jī)制很復(fù)雜,例如為了分配一塊內(nèi)存,庫(kù)函數(shù)會(huì)按照一定的算法(具體的算法可以參考數(shù)據(jù)結(jié)構(gòu)/操作系統(tǒng))在堆內(nèi)存中搜索可用的足夠大的空間,如果沒有足夠大的空間(可能是由于內(nèi)存碎片太多),就有需要操作系統(tǒng)來(lái)重新整理內(nèi)存空間,這樣就有機(jī)會(huì)分到足夠大小的內(nèi)存,然后返回。顯然,堆的效率比棧要低得多。

棧由系統(tǒng)自動(dòng)分配,速度較快。但程序員是無(wú)法控制的。

堆是由new分配的內(nèi)存,一般速度比較慢,而且容易產(chǎn)生內(nèi)存碎片,不過(guò)用起來(lái)最方便。

(7)申請(qǐng)后系統(tǒng)的響應(yīng)

棧:只要棧的剩余空間大于所申請(qǐng)空間,系統(tǒng)將為程序提供內(nèi)存,否則將報(bào)異常提示棧溢出。

堆:首先應(yīng)該知道操作系統(tǒng)有一個(gè)記錄空閑內(nèi)存地址的鏈表,當(dāng)系統(tǒng)收到程序的申請(qǐng)時(shí),會(huì)遍歷該鏈表,尋找第一個(gè)空間大于所申請(qǐng)空間的堆結(jié)點(diǎn),然后將該結(jié)點(diǎn)從空閑結(jié)點(diǎn)鏈表中刪除,并將該結(jié)點(diǎn)的空間分配給程序。對(duì)于大多數(shù)系統(tǒng),會(huì)在這塊內(nèi)存空間中的首地址處記錄本次分配的大小,這樣,代碼中的delete語(yǔ)句才能正確的釋放本內(nèi)存空間。由于找到的堆結(jié)點(diǎn)的大小不一定正好等于申請(qǐng)的大小,系統(tǒng)會(huì)自動(dòng)的將多余的那部分重新放入空閑鏈表中。

(8)堆和棧中的存儲(chǔ)內(nèi)容

棧:在函數(shù)調(diào)用時(shí),第一個(gè)進(jìn)棧的是主函數(shù)中后的下一條指令(函數(shù)調(diào)用語(yǔ)句的下一條可執(zhí)行語(yǔ)句)的地址,然后是函數(shù)的各個(gè)參數(shù),在大多數(shù)的C編譯器中,參數(shù)是由右往左入棧的,然后是函數(shù)中的局部變量。注意靜態(tài)變量是不入棧的。當(dāng)本次函數(shù)調(diào)用結(jié)束后,局部變量先出棧,然后是參數(shù),最后棧頂指針指向最開始存的地址,也就是主函數(shù)中的下一條指令,程序由該點(diǎn)繼續(xù)運(yùn)行。棧中的內(nèi)存是在程序編譯完成以后就可以確定的,不論占用空間大小,還是每個(gè)變量的類型。

堆:一般是在堆的頭部用一個(gè)字節(jié)存放堆的大小。堆中的具體內(nèi)容由程序員安排。

(9)存取效率的比較

1 char s1[] = "a"; 2 char *s2 = "b";

a是在運(yùn)行時(shí)刻賦值的;而b是在編譯時(shí)就確定的但是,在以后的存取中,在棧上的數(shù)組比指針?biāo)赶虻淖址?例如堆)快。

(10)防止越界發(fā)生

無(wú)論是堆還是棧,都要防止越界現(xiàn)象的發(fā)生(除非你是故意使其越界),因?yàn)樵浇绲慕Y(jié)果要么是程序崩潰,要么是摧毀程序的堆、棧結(jié)構(gòu),產(chǎn)生以想不到的結(jié)果,就算是在你的程序運(yùn)行過(guò)程中,沒有發(fā)生上面的問(wèn)題,你還是要小心,說(shuō)不定什么時(shí)候就崩掉,那時(shí)候debug可是相當(dāng)困難的

數(shù)據(jù)存儲(chǔ)區(qū)域?qū)嵗?/h1>

此程序顯示了數(shù)據(jù)存儲(chǔ)區(qū)域?qū)嵗?#xff0c;在此程序中,使用了etext、edata和end3個(gè)外部全局變量,這是與用戶進(jìn)程相關(guān)的虛擬地址。在程序源代碼中列出了各數(shù)據(jù)的存儲(chǔ)位置,同時(shí)在程序運(yùn)行時(shí)顯示了各數(shù)據(jù)的運(yùn)行位置,下圖所示為程序運(yùn)行過(guò)程中各變量的存儲(chǔ)位置。

mem_add.c

1 /* mem_add.c演示了C語(yǔ)言中地址的分布情況 */2 3 #include <stdio.h>4 #include <stdlib.h>5 6 extern void afunc(void);7 extern etext, edata, end;8 9 int bss_var; /* 未初始化全局?jǐn)?shù)據(jù)存儲(chǔ)在BSS區(qū) */ 10 int data_var = 42; /* 初始化全局?jǐn)?shù)據(jù)區(qū)域存儲(chǔ)在數(shù)據(jù)區(qū) */ 11 #define SHW_ADDR(ID, I) printf("the %8s\t is at addr:%8x\n", ID, &I); /* 打印地址 */ 12 13 int main(int argc, char *argv[]) { 14 15 char *p, *b, *nb; 16 printf("Addr etext: %8x\t Addr edata %8x\t Addr end %8x\t\n", &etext, &edata, &end); 17 18 printf("\ntext Location:\n"); 19 SHW_ADDR("main", main); /* 查看代碼段main函數(shù)位置 */ 20 SHW_ADDR("afunc", afunc); /* 查看代碼段afunc函數(shù)位置 */ 21 printf("\nbss Location:\n"); 22 SHW_ADDR("bss_var", bss_var); /* 查看BSS段變量的位置 */ 23 printf("\ndata Location:\n"); 24 SHW_ADDR("data_var", data_var); /* 查看數(shù)據(jù)段變量的位置 */ 25 printf("\nStack Locations:\n"); 26 27 afunc(); 28 p = (char *)alloca(32); /* 從棧中分配空間 */ 29 if(p != NULL) { 30 SHW_ADDR("start", p); 31 SHW_ADDR("end", p+31); 32 } 33 34 b = (char *)malloc(32*sizeof(char)); /* 從堆中分配空間 */ 35 nb = (char *)malloc(16*sizeof(char)); /* 從堆中分配空間 */ 36 printf("\nHeap Locations:\n"); 37 printf("the Heap start: %p\n", b); /* 堆的起始位置 */ 38 printf("the Heap end: %p\n", (nb+16*sizeof(char))); /* 堆的結(jié)束位置 */ 39 printf("\nb and nb in Stack\n"); 40 41 SHW_ADDR("b", b); /* 顯示棧中數(shù)據(jù)b的位置 */ 42 43 SHW_ADDR("nb", nb); /* 顯示棧中數(shù)據(jù)nb的位置 */ 44 45 free(b); /* 釋放申請(qǐng)的空間 */ 46 free(nb); /* 釋放申請(qǐng)的空間 */ 47 }

?afunc.c

1 /* afunc.c */2 #include <stdio.h>3 #define SHW_ADDR(ID, I) printf("the %s\t is at addr:%p\n", ID, &I); /* 打印地址 */4 void afunc(void) {5 static int long level = 0; /* 靜態(tài)數(shù)據(jù)存儲(chǔ)在數(shù)據(jù)段中 */6 int stack_var; /* 局部變量存儲(chǔ)在棧區(qū) */7 8 if(++level == 5)9 return; 10 11 printf("stack_var%d is at: %p\n", level, &stack_var); 12 SHW_ADDR("stack_var in stack section", stack_var); 13 SHW_ADDR("level in data section", level); 14 15 afunc(); 16 }

?gcc mem_add.c afunc.c進(jìn)行編譯然后執(zhí)行輸出的可執(zhí)行的文件,可得到如下結(jié)果(本機(jī)有效):

然后可以根據(jù)地址的大小來(lái)進(jìn)行一個(gè)排序,并可視化:

如果運(yùn)行環(huán)境不一樣,運(yùn)行程序的地址與此將有差異,但是,各區(qū)域之間的相對(duì)關(guān)系不會(huì)發(fā)生變化。可以通過(guò)readelf命令來(lái)查看可執(zhí)行文件的詳細(xì)內(nèi)容。

readelf -a a.out

其他知識(shí)點(diǎn)

來(lái)看一個(gè)問(wèn)題,下面代碼的輸出結(jié)果是啥?

第一個(gè)文件code1.c

1 #include <stdio.h>2 #include <stdlib.h>3 4 char* toStr() {5 char *s = "abcdefghijk";6 return s;7 }8 9 int main(void) { 10 printf("%s\n", toStr()); 11 }

?第二個(gè)文件code2.c

1 #include <stdio.h>2 #include <stdlib.h>3 4 char* toStr() {5 char s[] = "abcdefghijk";6 return s;7 }8 9 int main(void) { 10 printf("%s\n", toStr()); 11 }

?其實(shí)我在用gcc編譯第二的時(shí)候已經(jīng)有warning了:

第一個(gè)可以正常輸出,而第二個(gè)要么亂碼,要么是空的。

兩段代碼都很簡(jiǎn)單,輸出一段字符,類型不同,一個(gè)是char*字符串,一個(gè)是char[]數(shù)據(jù)。

結(jié)果:第一個(gè)正確輸出,第二個(gè)輸出亂碼。

原因:在于局部變量的作用域和內(nèi)存分配的問(wèn)題,第一char*是指向一個(gè)常量,作用域?yàn)楹瘮?shù)內(nèi)部,被分配在程序的常量區(qū),直到整個(gè)程序結(jié)束才被銷毀,所以在程序結(jié)束前常量還是存在的。而第二個(gè)是數(shù)組存放的,作用域?yàn)楹瘮?shù)內(nèi)部,被分配在棧中,就會(huì)在函數(shù)調(diào)用結(jié)束后被釋放掉,這時(shí)你再調(diào)用,肯定就錯(cuò)誤了。

我發(fā)現(xiàn)了一個(gè)新的問(wèn)題,如果你把這兩個(gè)文件合成一個(gè)的話,第二個(gè)其實(shí)可以打印出正確的字符的,代碼如下:

1 /* toStr.c演示內(nèi)存分配問(wèn)題哦 */2 3 #include <stdio.h>4 #include <stdlib.h>5 6 char* toStr1() {7 char *s = "abcdefghijklmn";8 return s;9 } 10 11 char* toStr2() { 12 char s[] = "abcdefghijklmn"; 13 return s; 14 } 15 16 void printStr() { 17 int a[] = {1,2,3,4,5,6,7}; 18 } 19 20 int main(void) { 21 printf("調(diào)用toStr1()返回的結(jié)果: %s\n",toStr1()); 22 printf("調(diào)用toStr2()返回的結(jié)果: %s\n",toStr2()); 23 // printStr(); 24 exit(0); 25 26 }

?

不知道為啥,第二個(gè)還是可以正常打印的。但是只打印第二個(gè),或者先打印第二個(gè),然后在打印第一個(gè)的話,不輸出亂碼,倒是輸出空串。

顧名思義,局部變量就是在一個(gè)有限的范圍內(nèi)的變量,作用域是有限的,對(duì)于程序來(lái)說(shuō),在一個(gè)函數(shù)體內(nèi)部聲明的普通變量都是局部變量,局部變量會(huì)在棧上申請(qǐng)空間,函數(shù)結(jié)束后,申請(qǐng)的空間會(huì)自動(dòng)釋放。而全局變量是在函數(shù)體外申請(qǐng)的,會(huì)被存放在全局(靜態(tài)區(qū))上,知道程序結(jié)束后才會(huì)被結(jié)束,這樣它的作用域就是整個(gè)程序。靜態(tài)變量和全局變量的存儲(chǔ)方式相同,在函數(shù)體內(nèi)聲明為static就可以使此變量像全局變量一樣使用,不用擔(dān)心函數(shù)結(jié)束而被釋放。

  • 棧區(qū)(stack)—由編譯器自動(dòng)分配釋放,存放函數(shù)的參數(shù)值,局部變量的值等。其操作方式類似于數(shù)據(jù)結(jié)構(gòu)中的棧。
  • 堆區(qū)(heap)—一般由程序員分配釋放,若程序員不釋放,程序結(jié)束時(shí)可能由OS回收。注意它與數(shù)據(jù)結(jié)構(gòu)中的堆是兩回事,分配方式倒是類似于鏈表
  • 全局區(qū)(靜態(tài)區(qū))(static)—全局變量和靜態(tài)變量的存儲(chǔ)是放在一塊的,初始化的全局變量和靜態(tài)變量在一塊區(qū)域,未初始化的全局變量和未初始化的靜態(tài)   ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?變量在相鄰的另一塊區(qū)域。? 程序結(jié)束后由系統(tǒng)釋放。
  • 常量區(qū)—常量字符串就是放在這里的,直到程序結(jié)束后由系統(tǒng)釋放。上面的問(wèn)題就在這里!!!
  • 代碼區(qū)—存放函數(shù)體的二進(jìn)制代碼。

一般編譯器和操作系統(tǒng)實(shí)現(xiàn)來(lái)說(shuō),對(duì)于虛擬地址空間的最低(從0開始的幾K)的一段空間是未被映射的,也就是說(shuō)它在進(jìn)程空間中,但沒有賦予物理地址,不能被訪問(wèn)。這也就是對(duì)空指針的訪問(wèn)會(huì)導(dǎo)致crash的原因,因?yàn)榭罩羔樀牡刂肥?。至于為什么預(yù)留的不是一個(gè)字節(jié)而是幾K,是因?yàn)閮?nèi)存是分頁(yè)的,至少要一頁(yè);另外幾k的空間還可以用來(lái)捕捉使用空指針的情況。

char *a 與char a[] 的區(qū)別

char *d = "hello" 中的a是指向第一個(gè)字符‘a(chǎn)'的一個(gè)指針;char s[20] = "hello" 中數(shù)組名a也是執(zhí)行數(shù)組第一個(gè)字符'h'的指針。現(xiàn)執(zhí)行下列操作:strcat(d, s)。把字符串加到指針?biāo)傅淖执先?#xff0c;出現(xiàn)段錯(cuò)誤,本質(zhì)原因:*d="0123456789"存放在常量區(qū),是無(wú)法修的。而數(shù)組是存放在棧中,是可以修改的。兩者區(qū)別如下:

讀寫能力:char *a = "abcd"此時(shí)"abcd"存放在常量區(qū)。通過(guò)指針只可以訪問(wèn)字符串常量,而不可以改變它。而char a[20] = "abcd"; 此時(shí) "abcd"存放在棧。可以通過(guò)指針去訪問(wèn)和修改數(shù)組內(nèi)容。

賦值時(shí)刻:char *a = "abcd"是在編譯時(shí)就確定了(因?yàn)闉槌A?#xff09;。而char a[20] = "abcd"; 在運(yùn)行時(shí)確定

存取效率:char *a = "abcd"; 存于靜態(tài)存儲(chǔ)區(qū)。在棧上的數(shù)組比指針?biāo)赶蜃址臁R虼寺?#xff0c;而char a[20] = "abcd"存于棧上,快。
另外注意:char a[] = "01234",雖然沒有指明字符串的長(zhǎng)度,但是此時(shí)系統(tǒng)已經(jīng)開好了,就是大小為6-----'0' '1' '2' '3' '4' '5' '\0',(注意strlen(a)是不計(jì)'\0')

總結(jié)

以上是生活随笔為你收集整理的C语言内存管理超详解的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。