堆與棧
- 堆是大家共有的空間,分全局堆和局部堆。全局堆就是所有沒(méi)有分配的空間,局部堆就是用戶分配的空間。堆在操作系統(tǒng)對(duì)進(jìn)程初始化的時(shí)候分配,運(yùn)行過(guò)程中也可以向系統(tǒng)要額外的堆,但是記得用完了要還給操作系統(tǒng),要不然就是內(nèi)存泄漏。
- 棧是線程獨(dú)有的,保存其運(yùn)行狀態(tài)和局部自動(dòng)變量的。棧在線程開(kāi)始的時(shí)候初始化,每個(gè)線程的?;ハ嗒?dú)立。每個(gè)函數(shù)都有自己的棧,棧被用來(lái)在函數(shù)之間傳遞參數(shù)。操作系統(tǒng)在切換線程的時(shí)候會(huì)自動(dòng)的切換棧,就是切換SS/ESP寄存器。??臻g不需要在高級(jí)語(yǔ)言里面顯式的分配和釋放。
分配內(nèi)存時(shí)堆與棧的區(qū)別
棧是由編譯器自動(dòng)分配釋放,存放函數(shù)的參數(shù)值、局部變量的值等。操作方式類似于數(shù)據(jù)結(jié)構(gòu)中的棧
堆一般由程序員分配釋放,若不釋放,程序結(jié)束時(shí)可能由OS回收。注意這里說(shuō)是可能,并非一定。再?gòu)?qiáng)調(diào)一次,記得要釋放?。?!
棧區(qū)(stack) :
編譯器自動(dòng)分配釋放,主要存放函數(shù)的參數(shù)值,局部變量值等。windows下,棧內(nèi)存分配是一個(gè)常數(shù),超出了限制,提示stack overflow錯(cuò)誤堆區(qū)(heap):程序員手動(dòng)分配釋放,操作系統(tǒng)80%內(nèi)存
全局區(qū)或靜態(tài)區(qū):存放全局變量和靜態(tài)變量;程序結(jié)束時(shí)由系統(tǒng)釋放,分為全局初始化區(qū)和全局未初始化區(qū)
字符常量區(qū):常量字符串放與此,程序結(jié)束時(shí)由系統(tǒng)釋放
程序代碼區(qū):存放函數(shù)體的二進(jìn)制代碼
示例如下:
int a = 0; // 全局初始化區(qū)
char *p1; // 全局未初始化區(qū)
int main(int argc, char** argv) {
int b; // 棧
char s[] = "abc"; // 棧
char *p2; // 棧
char *p3 = "3210"; // 其中,"3210\0"常量區(qū),p3在棧區(qū)
static int c = 0; // 全局區(qū)
p1 = (char*)malloc(20); // 20個(gè)字節(jié)區(qū)域在堆區(qū)
strcpy(p1, "3210"); // "3210\0" 在常量區(qū),編譯器可能會(huì)優(yōu)化為和p3的指向同一塊區(qū)域
return 0;
}
C語(yǔ)言中內(nèi)存分配與釋放
動(dòng)態(tài)與靜態(tài)分配內(nèi)存
靜態(tài)內(nèi)存分配,分配內(nèi)存大小的是固定,問(wèn)題:1.很容易超出棧內(nèi)存的最大值 2.為了防止內(nèi)存不夠用會(huì)開(kāi)辟更多的內(nèi)存,容易浪費(fèi)內(nèi)存
動(dòng)態(tài)內(nèi)存分配,在程序運(yùn)行過(guò)程中,動(dòng)態(tài)指定需要使用的內(nèi)存大小,手動(dòng)釋放,釋放之后這些內(nèi)存還可以被重新使用
malloc和free函數(shù)
- malloc僅僅分配內(nèi)存,free僅僅回收內(nèi)存,并不執(zhí)行構(gòu)造和析構(gòu)函數(shù)
- malloc、free是函數(shù),可以覆蓋,C、C++中都可以使用
- malloc申請(qǐng)的內(nèi)存只能使用free釋放
- malloc和free是實(shí)現(xiàn)的函數(shù),并不是C語(yǔ)言的標(biāo)準(zhǔn)
malloc、calloc、realloc的使用
malloc() 函數(shù)
用來(lái)動(dòng)態(tài)地分配內(nèi)存空間,原型為:void* malloc (size_t size);分配成功返回指向該內(nèi)存的地址,失敗則返回 NULL。calloc() 函數(shù)
用來(lái)動(dòng)態(tài)地分配內(nèi)存空間并初始化為0,就是NULL。原型為:void* calloc (size_t num, size_t size);。calloc() 在內(nèi)存中動(dòng)態(tài)地分配 num 個(gè)長(zhǎng)度為 size 的連續(xù)空間,并將每一個(gè)字節(jié)都初始化為 0。所以它的結(jié)果是分配了 num * size 個(gè)字節(jié)長(zhǎng)度的內(nèi)存空間,并且每個(gè)字節(jié)的值都是0。分配成功返回指向該內(nèi)存的地址,失敗則返回 NULL。realloc() 函數(shù)
重新分配內(nèi)存,原型為:realloc(void *__ptr, size_t __size);,更改已經(jīng)配置的內(nèi)存空間,即更改由malloc()函數(shù)分配的內(nèi)存空間的大小。如果將分配的內(nèi)存減少,realloc僅僅是改變索引的信息。如果是將分配的內(nèi)存擴(kuò)大,則有以下情況:
1)如果當(dāng)前內(nèi)存段后面有需要的足夠大內(nèi)存空間,則直接擴(kuò)展這段內(nèi)存空間,realloc()將返回原指針。
2)如果當(dāng)前內(nèi)存段后面的空閑字節(jié)不夠,那么就使用堆中的第一個(gè)能夠滿足這一要求的內(nèi)存塊,將目前的數(shù)據(jù)復(fù)制到新的位置,并將原來(lái)的數(shù)據(jù)塊釋放掉,返回新的內(nèi)存塊位置。
3)如果申請(qǐng)失敗,將返回NULL,此時(shí),原來(lái)的指針仍然有效。
注意:如果調(diào)用成功,不管當(dāng)前內(nèi)存段后面的空閑空間是否滿足要求,都會(huì)釋放掉原來(lái)的指針,重新返回一個(gè)指針,雖然返回的指針有可能和原來(lái)的指針一樣,即不能再次釋放掉原來(lái)的指針。
- reallocf(), valloc()函數(shù)
在mac系統(tǒng)中,還有這兩個(gè)函數(shù),不常用。原型為:
void* reallocf(void *ptr, size_t size);
void* valloc(size_t size);
The
valloc()function allocates size bytes of memory and returns a pointer to the allocated memory. The allocated memory is aligned on a page boundary.The
reallocf()function is identical to the realloc() function, except that it will free the passed pointer when the requested memory cannot be allocated.
This is a FreeBSD specific API designed to ease the problems with traditional coding styles for realloc causing memory leaks in libraries.
例子分析
#include <stdlib.h>
#include <stdio.h>
int main(int argc, char** argv) {
int a = 5;
long b = 10;
int * int_array = (int*)malloc(sizeof(int) * 8);
printf("%ld\n", int_array);
int_array[1] = 8;
free(int_array);
int * ints = (int*)malloc(sizeof(int) * 8);
printf("%ld\n", ints);
int * is = (int*)realloc(ints, sizeof(int) * 4);
printf("%ld\n", is);
int * iis = (int*)realloc(is, sizeof(int) * 16);
printf("%ld\n", iis);
//free(ints);
//free(is);
free(iis);
return 0;
}
輸出結(jié)果如下:
由圖可知,前三次的內(nèi)存地址輸出是一致的,第四次輸出了不一樣的內(nèi)存地址。當(dāng)然第二次的內(nèi)存地址跟第一次輸出的內(nèi)存地址一樣是因?yàn)榫幾g優(yōu)化的一些原因,大家可以嘗試不同gcc版本,使用不同的-O級(jí)別。第三次輸出一致的內(nèi)存地址,是因?yàn)樾路峙涞膬?nèi)存小于原來(lái)分配的內(nèi)存長(zhǎng)度,于是內(nèi)存地址還是一樣的,但是原來(lái)的指針已經(jīng)被釋放掉了。第四次則是內(nèi)存不足,所以重新分配到了一個(gè)新的內(nèi)存地址,原來(lái)的指針也沒(méi)釋放掉。讀者可以把注釋的free代碼放開(kāi),自己編譯看一下結(jié)果。
C++的new與delete
C++中是通過(guò)new和delete操作符進(jìn)行動(dòng)態(tài)內(nèi)存管理。雖然是C++中的動(dòng)態(tài)內(nèi)存管理,但是與C語(yǔ)言中的四個(gè)函數(shù)不同,new和delete是C++中的操作符。標(biāo)準(zhǔn)中已經(jīng)定義好,由編譯器來(lái)實(shí)現(xiàn)分配和釋放內(nèi)存,不是函數(shù)庫(kù)里面的實(shí)現(xiàn),但是底層實(shí)現(xiàn)中也會(huì)調(diào)用基礎(chǔ)的malloc和free函數(shù)。
new/delete和malloc/free的區(qū)別和聯(lián)系:
- 它們都是動(dòng)態(tài)管理內(nèi)存的入口
- malloc/free是C/C++標(biāo)準(zhǔn)庫(kù)的函數(shù),new/delete是C++操作符
- malloc/free只是動(dòng)態(tài)分配內(nèi)存空間/釋放空間。而new/delete除了分配空間還會(huì)調(diào)用構(gòu)造析構(gòu)函數(shù)進(jìn)行初始化與清理(清理成員)
- malloc/free需要手動(dòng)計(jì)算類型大小且返回值為void*,new/delete可自己計(jì)算類型的大小對(duì)應(yīng)類型的指針
- new/delete的底層調(diào)用了malloc/free
- malloc/free申請(qǐng)空間后得判空,new/delete則不需要,之前是返回空指針,現(xiàn)在引發(fā)異常std:bad_alloc.
- new直接跟類型,malloc傳入節(jié)數(shù)個(gè)數(shù)
例子分析
1.C+11之前,可以初始化簡(jiǎn)單變量:
int *p = new int(12);
delete p;
double *dp = new double(4.5);
delete[] dp;
2.C++11之后,可使用列表初始化:
struct point{double x,int y,doble z};
point *p = new point{1,2,3};
delete p;
int *array = new int []{1,2,3,4};
delete[] array;
3.使用new 和delete時(shí),分別調(diào)用的是如下函數(shù):
void * operator new(std::size_t);
void * operator new[](std::size_t);
void * operator delete(std::size_t);
void * operator delete[](std::size_t);
例如1中的示例:
int *p = new int;
int *p = new (sizeof(int));
第三方不同的malloc/free實(shí)現(xiàn)
C語(yǔ)言的標(biāo)準(zhǔn)函數(shù)庫(kù)除了gcc libc之外,還有許多其他的實(shí)現(xiàn)。這里專門介紹一下malloc的其他實(shí)現(xiàn)。主要是ptmalloc,tcmalloc,jemalloc。這三種方式的實(shí)現(xiàn)各有自己的優(yōu)缺點(diǎn):
1.作為基礎(chǔ)庫(kù)的ptmalloc是最為穩(wěn)定的內(nèi)存管理器,無(wú)論在什么環(huán)境下都能適應(yīng),但是分配效率相對(duì)較低
2.而tcmalloc針對(duì)多核情況有所優(yōu)化,性能有所提高,但是內(nèi)存占用稍高,大內(nèi)存分配容易出現(xiàn)CPU飆升
3.jemalloc的內(nèi)存占用更高,但是在多核多線程下的表現(xiàn)也最為優(yōu)異
具體需要使用哪一個(gè)庫(kù),需要按照自己的業(yè)務(wù)需求來(lái),脫離業(yè)務(wù)需求談技術(shù)的都是耍流氓。