詳解C標(biāo)準(zhǔn)庫(kù)堆內(nèi)存函數(shù)
概述
C標(biāo)準(zhǔn)庫(kù)堆內(nèi)存函數(shù)有4個(gè):malloc、free、calloc、realloc,其函數(shù)聲明放在了#include <stdlib.h>中,主要用來(lái)申請(qǐng)和釋放堆內(nèi)存。
堆內(nèi)存的申請(qǐng)和釋放(wiki,chs),需要發(fā)起系統(tǒng)調(diào)用,會(huì)帶來(lái)昂貴的上下文切換(用戶(hù)態(tài)切換到內(nèi)核態(tài)),十分耗時(shí)。另外,這些過(guò)程可能是帶鎖的,難以并行化。
對(duì)于操作系統(tǒng)而言,內(nèi)存管理的基本單位是頁(yè)(通常為4K),而不是需要4 Bytes時(shí),就給你分配4 Bytes,釋放4 Bytes時(shí),就給你釋放4 Bytes。
因此,為了提升效率,操作系統(tǒng)會(huì)調(diào)用系統(tǒng)api(windows上是VirtualAlloc、VirtualFree,其他平臺(tái)是mmap、munmap)來(lái)實(shí)現(xiàn)一個(gè)Ansi C內(nèi)存分配器(工作在用戶(hù)態(tài)),供C標(biāo)準(zhǔn)庫(kù)堆內(nèi)存函數(shù)來(lái)使用。
不同操作系統(tǒng)Ansi C內(nèi)存分配器實(shí)現(xiàn)方案有所不同
- windows:MSVCRT.DLL中使用NT heap實(shí)現(xiàn)
- linux:glibc中使用ptmalloc實(shí)現(xiàn)
- Android:使用jemalloc實(shí)現(xiàn)
內(nèi)存碎片與碎片整理
(1)內(nèi)存碎片(fragmentation):即空閑內(nèi)存不能被利用。分為外部碎片(在分配單元間的未使用的內(nèi)存);內(nèi)部碎片(在分配單元中未使用的內(nèi)存)
(2)內(nèi)存碎片的罪魁禍?zhǔn)拙褪切K內(nèi)存的頻繁分配
(3)內(nèi)存碎片無(wú)法避免,只能通過(guò)內(nèi)存分配器算法來(lái)減少,例如:接近大小的內(nèi)存就近分配,釋放時(shí)能合并就合并,從而減少碎片
(4)上面講的內(nèi)存碎片指的是虛擬內(nèi)存碎片,OS是不管的,OS只管物理內(nèi)存。
(5)平時(shí)我們說(shuō)的內(nèi)存碎片整理(defragment)或內(nèi)存緊縮(memory compaction),是指OS對(duì)物理內(nèi)存進(jìn)行的碎片整理,把分開(kāi)小的物理內(nèi)存頁(yè)移動(dòng)在一起形成一個(gè)大的整塊。
OS整理完物理內(nèi)存后,會(huì)用新的物理內(nèi)存地址來(lái)更新虛擬內(nèi)存與物理內(nèi)存映射表,這些對(duì)于上層邏輯都是透明的。
虛擬內(nèi)存是不能進(jìn)行碎片整理的,主要原因是碎片整理會(huì)移動(dòng)內(nèi)存,上層邏輯的指針地址確還是指向老的地址,這會(huì)導(dǎo)致致命錯(cuò)誤。
內(nèi)存分配器的好壞標(biāo)準(zhǔn)
(1)分配和釋放的效率
(2)內(nèi)存分配器的利用率。包括以下幾個(gè)方面:
① 內(nèi)存對(duì)齊導(dǎo)致的不可使用的內(nèi)存碎片(內(nèi)部碎片)
② 內(nèi)存碎片太嚴(yán)重,使得分配大塊內(nèi)存時(shí),找不到空閑塊,最后導(dǎo)致內(nèi)存分配失?。ㄍ獠克槠?/p>
③ 內(nèi)存頁(yè)始終有被使用,導(dǎo)致分配器無(wú)法及時(shí)釋放該頁(yè)的內(nèi)存占用,使得整個(gè)內(nèi)存分配器的內(nèi)存占用被撐得很大,縮不回去
void* malloc( size_t size )
形參size為要求分配的字節(jié)數(shù)。如果函數(shù)執(zhí)行成功,malloc返回獲得內(nèi)存空間的首地址;如果函數(shù)執(zhí)行失敗,那么返回值為NULL。
由于 malloc函數(shù)值的類(lèi)型為void型指針,因此,可以將其值類(lèi)型轉(zhuǎn)換后賦給任意類(lèi)型指針,這樣就可以通過(guò)操作該類(lèi)型指針來(lái)操作從堆上獲得的內(nèi)存空間。
需要注意的是,malloc函數(shù)分配得到的內(nèi)存空間是未初始化的??赏ㄟ^(guò)調(diào)用memset來(lái)將其初始化為全0。
int* p = (int *) malloc(sizeof(int)*100); if (p == NULL) { printf("Can't get memory!\n"); } memset(p, 0, sizeof(int)*100);
void free( void* ptr )
從堆上獲得的內(nèi)存,在程序結(jié)束之前,系統(tǒng)不會(huì)將其自動(dòng)釋放,需要程序員來(lái)自己管理,防止出現(xiàn)內(nèi)存泄露。
free(p); p = NULL;
void* calloc( size_t num, size_t size )
calloc函數(shù)的功能與malloc函數(shù)的功能相似,都是從堆分配內(nèi)存。
函數(shù)返回值為void*。如果執(zhí)行成功,從堆上獲得size * num大小的堆內(nèi)存,并返回該內(nèi)存塊的首地址。如果執(zhí)行失敗,函數(shù)返回NULL。
與malloc函數(shù)不同的是,calloc函數(shù)得到的內(nèi)存塊會(huì)被初始化為全0。由于提供了2個(gè)參數(shù),比較適合為數(shù)組申請(qǐng)空間,可以將size設(shè)置為數(shù)組元素的空間長(zhǎng)度,將num設(shè)置為數(shù)組的容量。
int* p = (int *) calloc(100, sizeof(int)); if (p == NULL) { printf("Can't get memory!\n"); }
void *realloc( void *ptr, size_t new_size )
為ptr重新分配大小為size的一塊內(nèi)存空間。下面是這個(gè)函數(shù)的工作流程:
① 如果ptr為NULL,則函數(shù)相當(dāng)于malloc(new_size),試著分配一塊大小為new_size的內(nèi)存,如果成功將地址返回,否則返回NULL。
② 如果ptr不為NULL,查看ptr是不是在堆中,如果不是的話(huà)會(huì)拋出realloc invalid pointer異常。如果ptr在堆中,則查看new_size大小。
(a)如果new_size大小為0,則相當(dāng)于free(ptr),將ptr指向的內(nèi)存空間釋放掉,返回NULL。
(b)如果new_size小于原大小,只有new_size大小的數(shù)據(jù)會(huì)保存,后面地址的數(shù)據(jù)可能會(huì)丟失;
(c)如果new_size等于原大小,什么都沒(méi)有做;
(d)如果new_size大于原大小,則查看ptr指向的位置還有沒(méi)有足夠的連續(xù)內(nèi)存空間,如果有的話(huà),分配更多的空間,返回的地址和ptr相同;
如果沒(méi)有的話(huà),在更大的空間內(nèi)查找,如果找到new_size大小的空間,將舊的內(nèi)容拷貝到新的內(nèi)存中,把舊的內(nèi)存釋放掉,則返回新地址,否則返回NULL。
int* p = (int*)malloc(sizeof(int)); *p = 3; printf("p=%p\n", p); // p=0000020B2966E310 printf("*p=%d\n", *p); // *p=3 p = (int*)realloc(p, sizeof(int)); // 什么也不做 printf("p=%p\n", p); // p=0000020B2966E310 printf("*p=%d\n", *p); // *p=3 p = (int*)realloc(p, 1024 * sizeof(int)); // 創(chuàng)建4KB的內(nèi)存塊 注:4KB為一個(gè)頁(yè)面的大小 printf("p=%p\n", p); // p=0000020B29673A50 注:由于不能在原來(lái)地址上擴(kuò)容,會(huì)將原來(lái)地址內(nèi)存釋放,并在新地址申請(qǐng)內(nèi)存塊 printf("*p=%d\n", *p); // *p=3 realloc(p, 0); // 相當(dāng)于free(p) p = NULL;
以上就是詳解C標(biāo)準(zhǔn)庫(kù)堆內(nèi)存函數(shù)的詳細(xì)內(nèi)容,更多關(guān)于C標(biāo)準(zhǔn)庫(kù)堆內(nèi)存函數(shù)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
詳解利用C語(yǔ)言如何實(shí)現(xiàn)簡(jiǎn)單的內(nèi)存池
這篇文章主要給大家介紹了關(guān)于C語(yǔ)言如何實(shí)現(xiàn)簡(jiǎn)單的內(nèi)存池的相關(guān)資料,設(shè)計(jì)內(nèi)存池的目標(biāo)是為了保證服務(wù)器長(zhǎng)時(shí)間高效的運(yùn)行,通過(guò)對(duì)申請(qǐng)空間小而申請(qǐng)頻繁的對(duì)象進(jìn)行有效管理,減少內(nèi)存碎片的產(chǎn)生,合理分配管理用戶(hù)內(nèi)存,需要的朋友可以參考下2021-08-08C++多態(tài)的實(shí)現(xiàn)機(jī)制深入理解
這篇文章主要介紹了C++多態(tài)的實(shí)現(xiàn)機(jī)制理解的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-07-07C/C++實(shí)現(xiàn)圖形學(xué)掃描線(xiàn)填充算法
這篇文章主要介紹了C/C++實(shí)現(xiàn)圖形學(xué)掃描線(xiàn)填充算法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-04-04C語(yǔ)言實(shí)現(xiàn)共享單車(chē)管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)共享單車(chē)管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-08-08C++中字符串與整型及浮點(diǎn)型轉(zhuǎn)換全攻略
C++算法刷題等過(guò)程中經(jīng)常會(huì)遇到字符串與數(shù)字類(lèi)型的轉(zhuǎn)換,在這其中雖然樸素的算法有不少,但是對(duì)于double等類(lèi)型還是可以說(shuō)遇到一些麻煩,所以今天就來(lái)說(shuō)說(shuō)使用C++標(biāo)準(zhǔn)庫(kù)中的函數(shù)實(shí)現(xiàn)這些功能。感興趣的小伙伴一起參與閱讀吧2021-09-09C++學(xué)習(xí)之智能指針中的unique_ptr與shared_ptr
吃獨(dú)食的unique_ptr與樂(lè)于分享的shared_ptr是C++中常見(jiàn)的兩個(gè)智能指針,本文主要為大家介紹了這兩個(gè)指針的使用以及智能指針使用的原因,希望對(duì)大家有所幫助2023-05-05關(guān)于c++11與c風(fēng)格路徑拼接的速度對(duì)比
這篇文章主要介紹了關(guān)于c++11與c風(fēng)格路徑拼接的速度對(duì)比分析,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-07-07