C语言中的内存分配
在任何程序設(shè)計(jì)環(huán)境及語言中,內(nèi)存管理都十分重要。在目前的計(jì)算機(jī)系統(tǒng)或嵌入式系統(tǒng)中,內(nèi)存資源仍然是有限的。因此在程序設(shè)計(jì)中,有效地管理內(nèi)存資源是程序員首先考慮的問題。
第1節(jié)主要介紹內(nèi)存管理基本概念,重點(diǎn)介紹C程序中內(nèi)存的分配,以及C語言編譯后的可執(zhí)行程序的存儲結(jié)構(gòu)和運(yùn)行結(jié)構(gòu),同時(shí)還介紹了堆空間和??臻g的用途及區(qū)別。
第2節(jié)主要介紹C語言中內(nèi)存分配及釋放函數(shù)、函數(shù)的功能,以及如何調(diào)用這些函數(shù)申請/釋放內(nèi)存空間及其注意事項(xiàng)。
3.1 內(nèi)存管理基本概念
3.1.1 C程序內(nèi)存分配
1.C程序結(jié)構(gòu)
下面列出C語言可執(zhí)行程序的基本情況(Linux 2.6環(huán)境/GCC4.0)。
| [root@localhost Ctest]# ls test -l???? //test為一個(gè)可執(zhí)行程序 -rwxr-xr-x? 1 root root 4868 Mar 26 08:10 test [root@localhost Ctest]# file test?//此文件基本情況 test: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.2.5, dynamically linked (uses shared libs), not stripped [root@localhost Ctest]# size test??//此二進(jìn)制可執(zhí)行文件結(jié)構(gòu)情況 //代碼區(qū)靜態(tài)數(shù)據(jù)/全局初始化數(shù)據(jù)區(qū) 未初始化數(shù)據(jù)區(qū) 十進(jìn)制總和 十六進(jìn)制總和 文件名 text?? data??? ?????bss??? dec??? hex?filename 906??? 284????? ????4?? ?1194??? 4aa?test |
可以看出,此可執(zhí)行程序在存儲時(shí)(沒有調(diào)入到內(nèi)存)分為代碼區(qū)(text)、數(shù)據(jù)區(qū)(data)和未初始化數(shù)據(jù)區(qū)(bss)3個(gè)部分。
(1)代碼區(qū)(text segment)。存放CPU執(zhí)行的機(jī)器指令(machine instructions)。通常,代碼區(qū)是可共享的(即另外的執(zhí)行程序可以調(diào)用它),因?yàn)閷τ陬l繁被執(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ù)):
| int?? maxcount = 99; |
使得變量maxcount根據(jù)其初始值被存儲到初始化數(shù)據(jù)區(qū)中。
| 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)算符而來,這個(gè)匯編運(yùn)算符標(biāo)志著一個(gè)塊的開始。BSS區(qū)的數(shù)據(jù)在程序開始執(zhí)行之前被內(nèi)核初始化為0或者空指針(NULL)。例如一個(gè)不在任何函數(shù)內(nèi)的聲明:
| long? sum[1000]; |
將變量sum存儲到未初始化數(shù)據(jù)區(qū)。
圖3-1所示為可執(zhí)行代碼存儲時(shí)結(jié)構(gòu)和運(yùn)行時(shí)結(jié)構(gòu)的對照圖。一個(gè)正在運(yùn)行著的C編譯程序占用的內(nèi)存分為代碼區(qū)、初始化數(shù)據(jù)區(qū)、未初始化數(shù)據(jù)區(qū)、堆區(qū)和棧區(qū)5個(gè)部分。
| (點(diǎn)擊查看大圖)圖3-1 C程序的內(nèi)存布局 |
(1)代碼區(qū)(text segment)。代碼區(qū)指令根據(jù)程序設(shè)計(jì)流程依次執(zhí)行,對于順序指令,則只會執(zhí)行一次(每個(gè)進(jìn)程),如果反復(fù),則需要使用跳轉(zhuǎn)指令,如果進(jìn)行遞歸,則需要借助棧來實(shí)現(xiàn)。
代碼區(qū)的指令中包括操作碼和要操作的對象(或?qū)ο蟮刂芬?#xff09;。如果是立即數(shù)(即具體的數(shù)值,如5),將直接包含在代碼中;如果是局部數(shù)據(jù),將在棧區(qū)分配空間,然后引用該數(shù)據(jù)地址;如果是BSS區(qū)和數(shù)據(jù)區(qū),在代碼中同樣將引用該數(shù)據(jù)地址。
(2)全局初始化數(shù)據(jù)區(qū)/靜態(tài)數(shù)據(jù)區(qū)(Data Segment)。只初始化一次。
(3)未初始化數(shù)據(jù)區(qū)(BSS)。在運(yùn)行時(shí)改變其值。
(4)棧區(qū)(stack)。由編譯器自動分配釋放,存放函數(shù)的參數(shù)值、局部變量的值等。其操作方式類似于數(shù)據(jù)結(jié)構(gòu)中的棧。每當(dāng)一個(gè)函數(shù)被調(diào)用,該函數(shù)返回地址和一些關(guān)于調(diào)用的信息,比如某些寄存器的內(nèi)容,被存儲到棧區(qū)。然后這個(gè)被調(diào)用的函數(shù)再為它的自動變量和臨時(shí)變量在棧區(qū)上分配空間,這就是C實(shí)現(xiàn)函數(shù)遞歸調(diào)用的方法。每執(zhí)行一次遞歸函數(shù)調(diào)用,一個(gè)新的??蚣芫蜁皇褂?#xff0c;這樣這個(gè)新實(shí)例棧里的變量就不會和該函數(shù)的另一個(gè)實(shí)例棧里面的變量混淆。
(5)堆區(qū)(heap)。用于動態(tài)內(nèi)存分配。堆在內(nèi)存中位于bss區(qū)和棧區(qū)之間。一般由程序員分配和釋放,若程序員不釋放,程序結(jié)束時(shí)有可能由OS回收。
之所以分成這么多個(gè)區(qū)域,主要基于以下考慮:
一個(gè)進(jìn)程在運(yùn)行過程中,代碼是根據(jù)流程依次執(zhí)行的,只需要訪問一次,當(dāng)然跳轉(zhuǎn)和遞歸有可能使代碼執(zhí)行多次,而數(shù)據(jù)一般都需要訪問多次,因此單獨(dú)開辟空間以方便訪問和節(jié)約空間。
臨時(shí)數(shù)據(jù)及需要再次使用的代碼在運(yùn)行時(shí)放入棧區(qū)中,生命周期短。
全局?jǐn)?shù)據(jù)和靜態(tài)數(shù)據(jù)有可能在整個(gè)程序執(zhí)行過程中都需要訪問,因此單獨(dú)存儲管理。
堆區(qū)由用戶自由分配,以便管理。
下面通過一段簡單的代碼來查看C程序執(zhí)行時(shí)的內(nèi)存分配情況。相關(guān)數(shù)據(jù)在運(yùn)行時(shí)的位置如注釋所述。
| //main.cpp int a = 0;????//a在全局已初始化數(shù)據(jù)區(qū) char *p1;????//p1在BSS區(qū)(未初始化全局變量) main() { int b;????//b在棧區(qū) char s[] = "abc";?//s為數(shù)組變量,存儲在棧區(qū), //"abc"為字符串常量,存儲在已初始化數(shù)據(jù)區(qū) char *p1,p2;??//p1、p2在棧區(qū) char *p3 = "123456";?//123456\0在已初始化數(shù)據(jù)區(qū),p3在棧區(qū) static int c =0;??//C為全局(靜態(tài))數(shù)據(jù),存在于已初始化數(shù)據(jù)區(qū) //另外,靜態(tài)數(shù)據(jù)會自動初始化 p1 = (char *)malloc(10);//分配得來的10個(gè)字節(jié)的區(qū)域在堆區(qū) p2 = (char *)malloc(20);//分配得來的20個(gè)字節(jié)的區(qū)域在堆區(qū) free(p1); free(p2); } |
2.內(nèi)存分配方式
在C語言中,對象可以使用靜態(tài)或動態(tài)的方式分配內(nèi)存空間。
靜態(tài)分配:編譯器在處理程序源代碼時(shí)分配。
動態(tài)分配:程序在執(zhí)行時(shí)調(diào)用malloc庫函數(shù)申請分配。
靜態(tài)內(nèi)存分配是在程序執(zhí)行之前進(jìn)行的因而效率比較高,而動態(tài)內(nèi)存分配則可以靈活的處理未知數(shù)目的。
靜態(tài)與動態(tài)內(nèi)存分配的主要區(qū)別如下:
靜態(tài)對象是有名字的變量,可以直接對其進(jìn)行操作;動態(tài)對象是沒有名字的變量,需要通過指針間接地對它進(jìn)行操作。
靜態(tài)對象的分配與釋放由編譯器自動處理;動態(tài)對象的分配與釋放必須由程序員顯式地管理,它通過malloc()和free兩個(gè)函數(shù)(C++中為new和delete運(yùn)算符)來完成。
以下是采用靜態(tài)分配方式的例子。
| int a=100; |
此行代碼指示編譯器分配足夠的存儲區(qū)以存放一個(gè)整型值,該存儲區(qū)與名字a相關(guān)聯(lián),并用數(shù)值100初始化該存儲區(qū)。
以下是采用動態(tài)分配方式的例子。
| p1 = (char *)malloc(10*sizeof(int));//分配得來得10*4字節(jié)的區(qū)域在堆區(qū) |
此行代碼分配了10個(gè)int類型的對象,然后返回對象在內(nèi)存中的地址,接著這個(gè)地址被用來初始化指針對象p1,對于動態(tài)分配的內(nèi)存唯一的訪問方式是通過指針間接地訪問,其釋放方法為:
| free(p1); |
3.1.2 棧和堆的區(qū)別
前面已經(jīng)介紹過,棧是由編譯器在需要時(shí)分配的,不需要時(shí)自動清除的變量存儲區(qū)。里面的變量通常是局部變量、函數(shù)參數(shù)等。堆是由malloc()函數(shù)(C++語言為new運(yùn)算符)分配的內(nèi)存塊,內(nèi)存釋放由程序員手動控制,在C語言為free函數(shù)完成(C++中為delete)。棧和堆的主要區(qū)別有以下幾點(diǎn):
(1)管理方式不同。
棧編譯器自動管理,無需程序員手工控制;而堆空間的申請釋放工作由程序員控制,容易產(chǎn)生內(nèi)存泄漏。
(2)空間大小不同。
棧是向低地址擴(kuò)展的數(shù)據(jù)結(jié)構(gòu),是一塊連續(xù)的內(nèi)存區(qū)域。這句話的意思是棧頂?shù)牡刂泛蜅5淖畲笕萘渴窍到y(tǒng)預(yù)先規(guī)定好的,當(dāng)申請的空間超過棧的剩余空間時(shí),將提示溢出。因此,用戶能從棧獲得的空間較小。
堆是向高地址擴(kuò)展的數(shù)據(jù)結(jié)構(gòu),是不連續(xù)的內(nèi)存區(qū)域。因?yàn)橄到y(tǒng)是用鏈表來存儲空閑內(nèi)存地址的,且鏈表的遍歷方向是由低地址向高地址。由此可見,堆獲得的空間較靈活,也較大。棧中元素都是一一對應(yīng)的,不會存在一個(gè)內(nèi)存塊從棧中間彈出的情況。
(3)是否產(chǎn)生碎片。
對于堆來講,頻繁的malloc/free(new/delete)勢必會造成內(nèi)存空間的不連續(xù),從而造成大量的碎片,使程序效率降低(雖然程序在退出后操作系統(tǒng)會對內(nèi)存進(jìn)行回收管理)。對于棧來講,則不會存在這個(gè)問題。
(4)增長方向不同。
堆的增長方向是向上的,即向著內(nèi)存地址增加的方向;棧的增長方向是向下的,即向著內(nèi)存地址減小的方向。
(5)分配方式不同。
堆都是程序中由malloc()函數(shù)動態(tài)申請分配并由free()函數(shù)釋放的;棧的分配和釋放是由編譯器完成的,棧的動態(tài)分配由alloca()函數(shù)完成,但是棧的動態(tài)分配和堆是不同的,他的動態(tài)分配是由編譯器進(jìn)行申請和釋放的,無需手工實(shí)現(xiàn)。
(6)分配效率不同。
棧是機(jī)器系統(tǒng)提供的數(shù)據(jù)結(jié)構(gòu),計(jì)算機(jī)會在底層對棧提供支持:分配專門的寄存器存放棧的地址,壓棧出棧都有專門的指令執(zhí)行。堆則是C函數(shù)庫提供的,它的機(jī)制很復(fù)雜,例如為了分配一塊內(nèi)存,庫函數(shù)會按照一定的算法(具體的算法可以參考數(shù)據(jù)結(jié)構(gòu)/操作系統(tǒng))在堆內(nèi)存中搜索可用的足夠大的空間,如果沒有足夠大的空間(可能是由于內(nèi)存碎片太多),就有需要操作系統(tǒng)來重新整理內(nèi)存空間,這樣就有機(jī)會分到足夠大小的內(nèi)存,然后返回。顯然,堆的效率比棧要低得多。
3.1.3 Linux數(shù)據(jù)類型大小
在Linux操作系統(tǒng)下使用GCC進(jìn)行編程,目前一般的處理器為32位字寬,下面是/usr/include/limit.h文件對Linux下數(shù)據(jù)類型的限制及存儲字節(jié)大小的說明。
| /* We don't have #include_next.?? Define ANSI for standard 32-bit words.? */ /* These assume 8-bit 'char's, 16-bit 'short int's,?? and 32-bit 'int's and 'long int's.? */ |
1.char數(shù)據(jù)類型
char類型數(shù)據(jù)所占內(nèi)存空間為8位。其中有符號字符型變量取值范圍為?128~127,無符號型字符變量取值范圍為0~255。其限制如下:
| /* Number of bits in a 'char'.?*/ #? define CHAR_BIT?8??????????//所占字節(jié)數(shù) /* Minimum and maximum values a 'signed char' can hold.? */??//有符號字符型范圍 #? define SCHAR_MIN?(-128) #? define SCHAR_MAX?127 /* Maximum value an 'unsigned char' can hold.? (Minimum is 0.)? */?//無符號字符型范圍 #? define UCHAR_MAX?255 /* Minimum and maximum values a 'char' can hold.? */ #? ifdef __CHAR_UNSIGNED__ #?? define CHAR_MIN?0 #?? define CHAR_MAX?UCHAR_MAX #? else #?? define CHAR_MIN?SCHAR_MIN #?? define CHAR_MAX?SCHAR_MAX #? endif |
2.short int數(shù)據(jù)類型
short int類型數(shù)據(jù)所占內(nèi)存空間為16位。其中有符號短整型變量取值范圍為?32768~32767,無符號短整型變量取值范圍為0~65535。其限制如下:
| /* Minimum and maximum values a 'signed short int' can hold.? */?// 有符號短整型范圍 #? define SHRT_MIN?(-32768) #? define SHRT_MAX?32767 /* Maximum value an 'unsigned short int' can hold.? (Minimum is 0.)? */// 無符號短整型范圍 #? define USHRT_MAX?65535 |
3.int數(shù)據(jù)類型
int類型數(shù)據(jù)所占內(nèi)存空間為32位。其中有符號整型變量取值范圍為?2147483648~2147483647,無符號型整型變量取值范圍為0~4294967295U。其限制如下:
| /* Minimum and maximum values a 'signed int' can hold.? */??//整形范圍 #? define INT_MIN?(-INT_MAX - 1)????? #? define INT_MAX?2147483647 /* Maximum value an 'unsigned int' can hold.? (Minimum is 0.)? */?//無符號整形范圍 #? define UINT_MAX?4294967295U |
4.long int數(shù)據(jù)類型
隨著宏__WORDSIZE值的改變,long int數(shù)據(jù)類型的大小也會發(fā)生改變。如果__WORDSIZE的值為32,則long int和int類型一樣,占有32位。在Linux GCC4.0-i386版本中,默認(rèn)情況下__WORDSIZE的值為32。其定義如下:
| //come from /usr/include/bits/wordsize.h #define __WORDSIZE?32 |
在64位機(jī)器上,如果__WORDSIZE的值為64, long int類型數(shù)據(jù)所占內(nèi)存空間為64位。其中有長整型變量取值范圍為-9223372036854775808L~3372036854775807L,無符號長整型變量取值范圍為0~18446744073709551615UL。其限制如下:
| /* Minimum and maximum values a 'signed long int' can hold.? */?//有符號長整形范圍 #? if __WORDSIZE == 64 #?? define LONG_MAX?9223372036854775807L #? else #?? define LONG_MAX?2147483647L #? endif #? define LONG_MIN?(-LONG_MAX - 1L) /* Maximum value an 'unsigned long int' can hold.? (Minimum is 0.)? *///無符號長整形范圍 #? if __WORDSIZE == 64 #?? define ULONG_MAX?18446744073709551615UL #? else #?? define ULONG_MAX?4294967295UL #? endif |
5.long long int數(shù)據(jù)類型
在C99中,還定義了long long int數(shù)據(jù)類型。其數(shù)據(jù)類型限制如下:
| #? ifdef __USE_ISOC99 /* Minimum and maximum values a 'signed long long int' can hold.? *///無符號長長整形范圍 #?? define LLONG_MAX?9223372036854775807LL #?? define LLONG_MIN?(-LLONG_MAX - 1LL) /* Maximum value an 'unsigned long long int' can hold.? (Minimum is 0.)? *///有符號長長整形范圍 #?? define ULLONG_MAX?18446744073709551615ULL #? endif /* ISO C99 */ |
3.1.4 數(shù)據(jù)存儲區(qū)域?qū)嵗?/span>
此程序顯示了數(shù)據(jù)存儲區(qū)域?qū)嵗?#xff0c;在此程序中,使用了etext、edata和end3個(gè)外部全局變量,這是與用戶進(jìn)程相關(guān)的虛擬地址。
在程序源代碼中列出了各數(shù)據(jù)的存儲位置,同時(shí)在程序運(yùn)行時(shí)顯示了各數(shù)據(jù)的運(yùn)行位置,圖3-2所示為程序運(yùn)行過程中各變量的存儲位置。
| 圖3-2 函數(shù)運(yùn)行時(shí)各數(shù)據(jù)位置 |
主函數(shù)源代碼如下:
| [root@localhost linux_app]# cat mem_add.c #include #include #include #include extern void afunc(void); extern etext,edata,end; int bss_var;?????????????????????????? ?????//未初始化全局?jǐn)?shù)據(jù)存儲在BSS區(qū) int data_var=42;??????????????????????? ????//初始化全局?jǐn)?shù)據(jù)存儲在數(shù)據(jù)區(qū) #define SHW_ADR(ID,I) printf("the %8s\t is at adr:%8x\n",ID,&I);?//打印地址宏 int main(int argc,char *argv[]) { char *p,*b,*nb; printf("Adr etext:%8x\t Adr edata %8x\t Adr end %8x\t\n",&etext,&edata,&end); printf("\ntext Location:\n"); SHW_ADR("main",main);????????? ????//查看代碼段main函數(shù)位置 SHW_ADR("afunc",afunc);?????? ????//查看代碼段afunc函數(shù)位置 printf("\nbss Location:\n"); SHW_ADR("bss_var",bss_var);? ????/查看BSS段變量位置 printf("\ndata location:\n"); SHW_ADR("data_var",data_var);?? ??/查看數(shù)據(jù)段變量 printf("\nStack Locations:\n");? afunc(); p=(char *)alloca(32);????????? ????//從棧中分配空間 if(p!=NULL) { SHW_ADR("start",p); SHW_ADR("end",p+31); } b=(char *)malloc(32*sizeof(char)); ??//從堆中分配空間 nb=(char *)malloc(16*sizeof(char));??//從堆中分配空間 printf("\nHeap Locations:\n"); printf("the Heap start: %p\n",b);???//堆起始位置 printf("the Heap end:%p\n",(nb+16*sizeof(char)));//堆結(jié)束位置 printf("\nb and nb in Stack\n"); SHW_ADR("b",b);???????//顯示棧中數(shù)據(jù)b的位置 SHW_ADR("nb",nb); ??????//顯示棧中數(shù)據(jù)nb的位置 free(b);?????????//釋放申請的空間,以避免內(nèi)存泄漏 free(nb); ????????//釋放申請的空間,以避免內(nèi)存泄漏 } |
子函數(shù)源代碼如下:
| void afunc(void) { static int long level=0;??????? ??//靜態(tài)數(shù)據(jù)存儲在數(shù)據(jù)段中 int????? stack_var;?????????????? ??//局部變量,存儲在棧區(qū) if(++level==5) { return; } printf("stack_var is at:%p\n",&stack_var); //????? SHW_ADR("stack_var in stack section",stack_var); //????? SHW_ADR("Level in data section",level); afunc(); } |
函數(shù)運(yùn)行結(jié)果如下:
| [root@localhost linux_app]# gcc -o mem_add mem_add.c?//編譯 [root@localhost linux_app]# ./mem_add ????//運(yùn)行結(jié)果 Adr etext: 8048702?????? Adr edata? 8049950????? Adr end? 804995c text Location: the???? main???? is at adr: 8048418 the??? afunc???? is at adr: 8048611 bss Location: the? bss_var???? is at adr: 8049958 data location: the data_var???? is at adr: 804994c Stack Locations: the stack_var in stack section?? ?is at adr:bfbf6c44 the Level in data section??????? ?is at adr: 8049954 the stack_var in stack section?? ?is at adr:bfbf6c24 the Level in data section??????? ?is at adr: 8049954 the stack_var in stack section?? ?is at adr:bfbf6c04 the Level in data section??????? ?is at adr: 8049954 the stack_var in stack section?? ?is at adr:bfbf6be4 the Level in data section??????? ?is at adr: 8049954 the??? start???? is at adr:bfbf6c74 the????? end???? is at adr:bfbf6cf0 Heap Locations: the Heap start: 0x8453008 the Heap end:0x8453040 b and nb in Stack the??????? b???? is at adr:bfbf6c70 the?????? nb???? is at adr:bfbf6c6c |
如果運(yùn)行環(huán)境不一樣,運(yùn)行程序的地址與此將有差異,但是,各區(qū)域之間的相對關(guān)系不會發(fā)生變化??梢酝ㄟ^readelf命令來查看可執(zhí)行文件的詳細(xì)內(nèi)容。
| [root@localhost yangzongde]# readelf -a memadd |
3.2 內(nèi)存管理函數(shù)
3.2.1 malloc/free函數(shù)
Malloc()函數(shù)用來在堆中申請內(nèi)存空間,free()函數(shù)釋放原先申請的內(nèi)存空間。Malloc()函數(shù)是在內(nèi)存的動態(tài)存儲區(qū)中分配一個(gè)長度為size字節(jié)的連續(xù)空間。其參數(shù)是一個(gè)無符號整型數(shù),返回一個(gè)指向所分配的連續(xù)存儲域的起始地址的指針。當(dāng)函數(shù)未能成功分配存儲空間時(shí)(如內(nèi)存不足)則返回一個(gè)NULL指針。
由于內(nèi)存區(qū)域總是有限的,不能無限制地分配下去,而且程序應(yīng)盡量節(jié)省資源,所以當(dāng)分配的內(nèi)存區(qū)域不用時(shí),則要釋放它,以便其他的變量或程序使用。
這兩個(gè)函數(shù)的庫頭文件為:
| #include |
函數(shù)定義如下:
| void *malloc(size_t size)???//返回類型為空指針類型 void free(void *ptr) |
例如:
| int *p1,*p2; p1=(int *)malloc(10*sizeof(int)); p2=p1; …… free(p2) ;??????/*或者free(p1)*/ p1=NULL;???????/*或者p2=NULL */ |
malloc()函數(shù)返回值賦給p1,又把p1的值賦給p2,所以此時(shí)p1,p2都可作為free函數(shù)的參數(shù)。使用free()函數(shù)時(shí),需要特別注意下面幾點(diǎn):
(1)調(diào)用free()釋放內(nèi)存后,不能再去訪問被釋放的內(nèi)存空間。內(nèi)存被釋放后,很有可能該指針仍然指向該內(nèi)存單元,但這塊內(nèi)存已經(jīng)不再屬于原來的應(yīng)用程序,此時(shí)的指針為懸掛指針(可以賦值為NULL)。
(2)不能兩次釋放相同的指針。因?yàn)獒尫艃?nèi)存空間后,該空間就交給了內(nèi)存分配子程序,再次釋放內(nèi)存空間會導(dǎo)致錯(cuò)誤。也不能用free來釋放非malloc()、calloc()和realloc()函數(shù)創(chuàng)建的指針空間,在編程時(shí),也不要將指針進(jìn)行自加操作,使其指向動態(tài)分配的內(nèi)存空間中間的某個(gè)位置,然后直接釋放,這樣也有可能引起錯(cuò)誤。
(3)在進(jìn)行C語言程序開發(fā)中,malloc/free是配套使用的,即不需要的內(nèi)存空間都需要釋放回收。
下面是使用這兩個(gè)函數(shù)的一個(gè)例子。
| [root@localhost yangzongde]# cat malloc_example.c #include?????????????? //printf()????//(1)頭文件信息 #include????????????? //malloc()????//(2) int main(int argc,char* argv[],char* envp[])???//(3) { int count; int* array; if((array=(int *)malloc(10*sizeof(int)))==NULL) ?//(4)分配空間 { printf("malloc memory unsuccessful"); exit(1); } for (count=0;count<10;count++) ?????//(5) 賦值 { *array=count; array++; } for(count=9;count>=0;count--)?????????????? ???//(6)賦值 { array--; printf("%4d",*array); } printf("\n"); free(array); ???????//(7)釋放空間 array=NULL; ??????//(8)將指針置為空,避免不安全訪問 exit (0); } [root@localhost yangzongde]# gcc -o malloc_example malloc_example.c ?//編譯 [root@localhost yangzongde]# ./malloc_example ??????//運(yùn)行 9?? 8?? 7?? 6?? 5?? 4?? 3?? 2?? 1?? 0 |
在以上程序中,(1)句中包含stdio.h頭文件,從而在后面可以調(diào)用printf()函數(shù)。(2)句中包含stdlib.h頭文件,其是malloc()函數(shù)的頭文件。(3)句為函數(shù)的入口位置,此處采用Linux下編程標(biāo)準(zhǔn),返回值為int型,argc為參數(shù)個(gè)數(shù), argv[]為參數(shù),envp[]存放的是所有環(huán)境變量。(4)句動態(tài)分配了10個(gè)整型存儲區(qū)域,此語句可以分為以下幾步。
① 分配10個(gè)整型的連續(xù)存儲空間,并返回一個(gè)指向其起始地址的整型指針。
② 把此整型指針地址賦給array。
③ 檢測返回值是否為NULL。
(5)、(6)句為數(shù)組賦值并打印輸出,以免內(nèi)存泄漏。(7)句調(diào)用free()函數(shù)釋放內(nèi)存空間。(8)句將一個(gè)NULL指針傳遞給array,雖然在很多情況下可以不用此句,但這樣處理可以避免此指針成為野指針。
在C++中,使用new和delete運(yùn)算符來實(shí)現(xiàn)內(nèi)存的分配和釋放,使用new/delete運(yùn)算符實(shí)現(xiàn)內(nèi)存管理比使用malloc/free函數(shù)更有優(yōu)越性。new/delete運(yùn)算符定義如下:
| static void* operator new(size_t sz);?????//new運(yùn)算符 static void? operator delete(void* p); ?????//delete運(yùn)算符 |
下面是一段C++程序代碼:
| void UseNewDelete(void) { Obj? *a = new Obj;?????? ????//申請動態(tài)內(nèi)存并且初始化 //… delete a;??????????????? ???//清除并且釋放內(nèi)存 } |
下面詳細(xì)介紹C++中new/delete運(yùn)算符的使用方法。
| class A { public: A() ?{?? cout<<"A is here!"<<ENDL;&NBSP;&NBSP; 構(gòu)造函數(shù) ~A()?{?? cout<<"A is dead!"<<ENDL;&NBSP;&NBSP; }??="" 析構(gòu)函數(shù) private: int i; }; A* pA=new A;?????//調(diào)用new運(yùn)算符申請空間 delete pA;??????//刪除pA |
其中,語句new A完成了以下兩個(gè)功能:
(1)調(diào)用運(yùn)算符new,在自由存儲區(qū)分配一個(gè)sizeof(A)大小的內(nèi)存空間。
(2)調(diào)用構(gòu)造函數(shù)A(),在這塊內(nèi)存空間上初始化對象。
當(dāng)然,delete pA完成相反的兩件事:
(1)調(diào)用析構(gòu)函數(shù)~A(),銷毀對象。
(2)調(diào)用運(yùn)算符delete,釋放內(nèi)存。
由此可以看出,運(yùn)算符new和delete提供了動態(tài)分配和釋放存儲區(qū)的功能。它們的作用相當(dāng)于C語言的malloc()和free()函數(shù),但是性能更為優(yōu)越。使用new比使用malloc()有以下幾個(gè)優(yōu)點(diǎn):
(1)new自動計(jì)算要分配給對象的內(nèi)存空間大小,不使用sizeof運(yùn)算符,簡單,而且可以避免錯(cuò)誤。
(2)自動地返回正確的指針類型,不用進(jìn)行強(qiáng)制類型轉(zhuǎn)換。
(3)用構(gòu)造函數(shù)給分配的對象進(jìn)行初始化。
但是,使用malloc函數(shù)和new分配內(nèi)存的時(shí)候,本身并沒有對這塊內(nèi)存空間做清零等任何動作。因此,申請內(nèi)存空間后,其返回的新分配的內(nèi)存是沒有零填充的,程序員需要使用memset()函數(shù)來初始化內(nèi)存。
3.2.2 realloc--更改已經(jīng)配置的內(nèi)存空間
realloc()函數(shù)用來從堆上分配內(nèi)存,當(dāng)需要擴(kuò)大一塊內(nèi)存空間時(shí),realloc()試圖直接從堆上當(dāng)前內(nèi)存段后面的字節(jié)中獲得更多的內(nèi)存空間,如果能夠滿足,則返回原指針;如果當(dāng)前內(nèi)存段后面的空閑字節(jié)不夠,那么就使用堆上第一個(gè)能夠滿足這一要求的內(nèi)存塊,將目前的數(shù)據(jù)復(fù)制到新的位置,而將原來的數(shù)據(jù)塊釋放掉。如果內(nèi)存不足,重新申請空間失敗,則返回NULL。此函數(shù)定義如下:
| void *realloc(void *ptr,size_t size) |
參數(shù)ptr為先前由malloc、calloc和realloc所返回的內(nèi)存指針,而參數(shù)size為新配置的內(nèi)存大小。其庫頭文件為:
| #include<stdlib.h> |
當(dāng)調(diào)用realloc()函數(shù)重新分配內(nèi)存時(shí),如果申請失敗,將返回NULL,此時(shí)原來指針仍然有效,因此在程序編寫時(shí)需要進(jìn)行判斷,如果調(diào)用成功,realloc()函數(shù)會重新分配一塊新內(nèi)存,并將原來的數(shù)據(jù)拷貝到新位置,返回新內(nèi)存的指針,而釋放掉原來指針(realloc()函數(shù)的參數(shù)指針)指向的空間,原來的指針變?yōu)椴豢捎?#xff08;即不需要再釋放,也不能再釋放),因此,一般不使用以下語句:
| ptr=realloc(ptr,new_amount) |
如果內(nèi)存減少,malloc僅僅改變索引信息,但并不代表被減少的部分還可以訪問,這一部分內(nèi)存將交給系統(tǒng)內(nèi)存分配子程序。
下面是一個(gè)使用relloc函數(shù)的實(shí)例。
| [root@localhost yangzongde]# cat realloc_example.c #include <stdio.h> #include <stdlib.h> int main (int argc,char* argv[],char* envp[])???//(1)主函數(shù) { int input; int n; int *numbers1; int *numbers2; numbers1=NULL; ???? if((numbers2=(int *)malloc(5*sizeof(int)))==NULL)?//(2)numbers2指針申請空間 { printf("malloc memory unsuccessful"); //free(numbers2); numbers2=NULL; exit(1); } for (n=0;n<5;n++) ??????//(3)初始化 { *(numbers2+n)=n; printf("numbers2's data: %d\n",*(numbers2+n)); } ???? printf("Enter an integer value you want to remalloc ( enter 0 to stop)\n");//(4)新申請空間大小 scanf ("%d",&input); numbers1=(int *)realloc(numbers2,(input+5)*sizeof(int));??//(5)重新申請空間 if (numbers1==NULL) { printf("Error (re)allocating memory"); exit (1); } ???? for(n=0;n<5;n++)???????//(6)這5個(gè)數(shù)是從numbers2拷貝而來 { printf("the numbers1s's data copy from numbers2: %d\n",*(numbers1+n)); } ???? for(n=0;n<input;n++) ??????//(7)新數(shù)據(jù)初始化 { *(numbers1+5+n)=n*2; printf ("nummber1's new data: %d\n",*(numbers1+5+n));?// numbers1++; } printf("\n"); free(numbers1);???????//(8)釋放numbers1 numbers1=NULL; // free(numbers2);?????//(9)不能再釋放numbers2 return 0; } [root@localhost yangzongde]# gcc -o realloc_example realloc_example.c [root@localhost yangzongde]# ./realloc_example numbers2's data: 0 numbers2's data: 1 numbers2's data: 2 numbers2's data: 3 numbers2's data: 4 Enter an integer value you want to remalloc ( enter 0 to stop)?//重新申請空間 5? the numbers1s's data copy from numbers2: 0 the numbers1s's data copy from numbers2: 1 the numbers1s's data copy from numbers2: 2 the numbers1s's data copy from numbers2: 3 the numbers1s's data copy from numbers2: 4 nummber1's new data: 0 nummber1's new data: 2 nummber1's new data: 4 nummber1's new data: 6 nummber1's new data: 8 |
此程序是一個(gè)簡單的重新申請內(nèi)存空間的實(shí)例,(1)為函數(shù)入口,前面已經(jīng)介紹過。(2)從堆空間中申請5個(gè)int空間,將返回地址賦給numbers2,如果返回值為NULL,將返回錯(cuò)誤信息,釋放numbers2并退出。(3)為新申請的空間初始化。(4)輸入需要增加的內(nèi)存數(shù)量。(5)調(diào)用realloc()函數(shù)重新申請內(nèi)存空間,重新申請內(nèi)存空間大小為原有空間大小加上用戶輸入的內(nèi)存空間數(shù)。如果申請失敗,將返回NULL,此時(shí)numbers2仍然有效。如果申請成功,將重新分配一塊大小合適的空間,并將新空間首地址賦給numbers1,同時(shí)將numbers2所指向的5個(gè)空間的數(shù)據(jù)復(fù)制到新的內(nèi)存空間中,釋放掉原來numbers2所指向的內(nèi)存空間。(6)打印從numbers2所指向的原空間拷貝的數(shù)據(jù),(7)句對新增加的空間進(jìn)行初始化。(8)句釋放number1所指向的新申請空間。(9)為注釋掉的代碼,提示讀者此時(shí)對原空間再次釋放,因?yàn)榈?#xff08;5)已經(jīng)完成了這一操作。
3.2.3 其他內(nèi)存管理函數(shù)calloc和alloca
1.calloc函數(shù)
calloc是malloc函數(shù)的簡單包裝,它的主要優(yōu)點(diǎn)是把動態(tài)分配的內(nèi)存進(jìn)行初始化,全部清零。其操作及語法類似malloc()函數(shù)。
| ptr=(struct data *)calloc (count,sizeof(strunt data))?//申請并初始化空間 |
下面是這個(gè)函數(shù)的實(shí)現(xiàn)描述:
| void *calloc(size_t nmemb,size_t size) { void *p; size_t total; total=nmemb *size; p=malloc(total);?????????//申請空間 if(p!=NULL) memset(p,'\0',total);??????//將其實(shí)始化為\0 return p; } |
2.alloca函數(shù)
alloca()函數(shù)用來在棧中分配size個(gè)字節(jié)的內(nèi)存空間,因此函數(shù)返回時(shí)會自動釋放掉空間。alloca函數(shù)定義及庫頭文件如下:
| /* Allocate a block that will be freed when the calling function exits.? */ extern void *alloca (size_t __size) __THROW;???//從棧中申請空間 |
返回值:若分配成功返回指針,失敗則返回NULL。
它與malloc()函數(shù)的區(qū)別主要在于:
alloca是向棧申請內(nèi)存,無需釋放,malloc申請的內(nèi)存位于堆中,最終需要函數(shù)free來釋放。
malloc函數(shù)并沒有初始化申請的內(nèi)存空間,因此調(diào)用malloc()函數(shù)之后,還需調(diào)用函數(shù)memset初始化這部分內(nèi)存空間;alloca則將初始化這部分內(nèi)存空間為0。
總結(jié)
- 上一篇: Python实战项目(一)刷网页访问量程
- 下一篇: 逆转一个链表