C/C++内存分配
1、brk()和sbrk()
// 成功時返回0,出錯時返回-1并設(shè)置errno為ENOMEM int brk(void *addr);// 成功時返回先前的堆結(jié)束位置。出錯時,返回(void *)-1并設(shè)置errno為ENOMEM void *sbrk(intptr_t increment);?
如上面兩個圖所示,堆是一個連續(xù)的內(nèi)存區(qū)域,在擴展時自下至上增長。mm_types.h定義的mm_struct結(jié)構(gòu)包含了堆在虛擬地址空間中的起始和當(dāng)前結(jié)束位置(start_brk和brk成員)。在start_brk和brk之間的是已映射區(qū)域,表示這部分的虛擬地址已經(jīng)映射到物理地址,程序可以直接訪問,而訪問未映射區(qū)域則會導(dǎo)致段錯誤。
brk()和sbrk()均可調(diào)整brk的值。brk()的addr參數(shù)指定了堆在虛擬地址空間中新的結(jié)束位置,而sbrk()通過增量increment調(diào)節(jié)堆的結(jié)束位置。
因此,當(dāng)堆需要擴展時,會調(diào)用brk()或sbrk()增大brk,而減小brk則意味著將堆頂部的一部分內(nèi)存釋放給系統(tǒng)(堆的收縮,trim)。brk的值總是系統(tǒng)頁長度的倍數(shù)(即一頁是用brk()能分配的最小內(nèi)存區(qū)域),而C標準庫函數(shù)(如malloc()和free())的任務(wù)便是將頁拆分成更小的區(qū)域,這樣可以避免頻繁的brk()系統(tǒng)調(diào)用帶來的開銷。
示例:
int main() {// sbrk(0)返回堆當(dāng)前的結(jié)束位置printf("%p\n", sbrk(0));sbrk(4088);int *p = (int *)sbrk(0);*p = 3;*(p + 1) = 3;// sbrk()是按頁調(diào)整brk的。這里p + 2指針“越界”了// *(p + 2) = 3;// 根據(jù)輸出得知,堆每次擴展33*4Kfor (int i = 0; i < 512; i++){char *s = (char *)malloc(1024);printf("loop: %p\n", sbrk(0));}return 0; }
注意:應(yīng)避免使用brk()和sbrk()。malloc()是可移植的、更好的分配內(nèi)存方法。
?
2、mallopt():調(diào)整內(nèi)存分配參數(shù),以控制內(nèi)存分配函數(shù)(如malloc())的行為。
// 將param指定的參數(shù)設(shè)置成value// 成功時返回1,錯誤時返回0且不設(shè)置errno int mallopt(int param, int value);
一些常用的param和對應(yīng)的value:
1)M_CHECK_ACTION:該參數(shù)控制當(dāng)檢測到不同類型的編程錯誤時(如釋放同一個指針兩次)glibc的行為。value最低三位的值決定了glibc的行為(根據(jù)Ubuntu 14.04的實際效果對man mallopt的內(nèi)容作了調(diào)整):
(1)第0位:設(shè)置該位后,在標準出錯上打印單行錯誤詳情消息,并調(diào)用abort()終止程序。該消息包含程序名、檢測到錯誤的內(nèi)存分配函數(shù)名、錯誤的簡要描述以及檢測到錯誤的內(nèi)存地址等。
(2)第1位:設(shè)置該位后,程序調(diào)用abort()終止。自glibc 2.4以后,若第0位也被設(shè)置,則在打印錯誤消息之后、終止程序之前,以backtrace()的方式打印堆棧跟蹤信息(stack? trace),并以/proc/[pid]/maps的方式打印進程的內(nèi)存映射情況。
(3)第2位(glibc 2.4以上):僅當(dāng)?shù)?位被設(shè)置時有效。設(shè)置該位后,單行的錯誤消息被簡化成只包含檢測到錯誤的函數(shù)名和對錯誤的簡短描述。程序然后繼續(xù)執(zhí)行。
其他位被忽略。因此value各個可能值的含義如下:0(忽略錯誤條件,繼續(xù)執(zhí)行,結(jié)果未定義。相當(dāng)于4)、1、2(相當(dāng)于6)、3、5(打印一個簡單的錯誤消息并繼續(xù)執(zhí)行)、7(打印簡單的錯誤消息、堆棧跟蹤信息和內(nèi)存映射情況,并終止程序)。一個例子:
int main(int argc, char *argv[]) {if (argc > 1) {if (mallopt(M_CHECK_ACTION, atoi(argv[1])) != 1) {fprintf(stderr, "mallopt() failed");exit(EXIT_FAILURE);}}char *p = (char *)malloc(1000);if (p == NULL) {fprintf(stderr, "malloc() failed");exit(EXIT_FAILURE);}free(p);printf("main(): returned from first free() call\n");free(p);printf("main(): returned from second free() call\n");exit(EXIT_SUCCESS); }編譯得到a.out,以不同的命令行參數(shù)運行:
~/programming$ ./a.out 1 main(): returned from first free() call *** Error in `./a.out': double free or corruption (top): 0x09653008 *** 已放棄 (核心已轉(zhuǎn)儲)~/programming$ ./a.out 0 main(): returned from first free() call main(): returned from second free() call# 一些環(huán)境變量可用來修改mallopt()控制的參數(shù)(若它們都設(shè)置了同一個參數(shù),則優(yōu)先取mallopt()的值) # 使用這些變量的好處是程序的源代碼不需要改變 # 比如,變量MALLOC_CHECK_、MALLOC_TRIM_THRESHOLD_、MALLOC_MMAP_MAX_、MALLOC_MMAP_THRESHOLD_ # 分別和mallopt()的M_CHECK_ACTION、M_TRIM_THRESHOLD、M_MMAP_MAX、M_MMAP_THRESHOLD參數(shù)對應(yīng) ~/programming$ MALLOC_CHECK_=0 ./a.out main(): returned from first free() call main(): returned from second free() call2)M_TRIM_THRESHOLD:當(dāng)堆頂部的連續(xù)空閑內(nèi)存足夠多時,free()使用brk()/sbrk()將這部分內(nèi)存釋放給系統(tǒng)。該參數(shù)指定在收縮堆之前,這塊空閑內(nèi)存需要達到的最小字節(jié)長度。默認值是128*1024。設(shè)置成-1則禁止收縮。
M_TRIM_THRESHOLD的調(diào)整需要權(quán)衡:若設(shè)置得太低,則增加了系統(tǒng)調(diào)用的次數(shù);若設(shè)置得太高,則浪費堆頂部的未用內(nèi)存。
3)M_MMAP_MAX:該參數(shù)指定可以用mmap()同時處理的內(nèi)存分配請求的最大數(shù)目。該參數(shù)存在的原因是一些系統(tǒng)只有數(shù)量有限的幾個內(nèi)部表可被mmap()使用,而過量使用會導(dǎo)致性能下降。默認值是65536。把它設(shè)置為0則禁止使用mmap()處理大的內(nèi)存分配請求(驗證:分兩種情況,將該參數(shù)設(shè)置為0和非0,再分別用malloc()分配一個大內(nèi)存塊。比較這兩個塊的起始地址。使用mmap()分配的內(nèi)存塊的地址會大得多,參照本文開始的圖)。
4)M_MMAP_THRESHOLD:mmap閥值。使用mmap()滿足大塊內(nèi)存(長度>=M_MMAP_THRESHOLD字節(jié))的分配,而不是通過brk()/sbrk()增大brk。mmap()分配的內(nèi)存不受RLIMIT_DATA限制(getrlimit())。默認值為128 * 1024。
使用mmap()分配內(nèi)存有明顯的好處:分配的內(nèi)存塊總是可以被獨立地釋放給系統(tǒng)。相比之下,堆僅在頂部內(nèi)存被釋放的情況下可以收縮。
但使用mmap()也有一些缺點:釋放的空間沒有放置到空閑列表以便被之后的分配操作重用;內(nèi)存可能被浪費,因為mmap()分配的內(nèi)存必須是頁對齊的。
如今的glibc默認使用一個動態(tài)的mmap閥值,其初始值為128*1024。當(dāng)一個長度大于當(dāng)前閥值且小于等于DEFAULT_MMAP_THRESHOLD_MAX的內(nèi)存塊被釋放時,閥值被向上調(diào)整為該塊的大小。使用動態(tài)mmap閥值時,M_TRIM_THRESHOLD也被動態(tài)地調(diào)整為mmap閥值的兩倍。不過,M_TRIM_THRESHOLD、M_TOP_PAD、M_MMAP_THRESHOLD或M_MMAP_MAX中的任何一個被設(shè)置時,mmap閥值的動態(tài)調(diào)整特性被禁用。
?
3、malloc_***
1)malloc_usable_size():獲得某個從堆中分配的內(nèi)存塊的大小。
// ptr必須是malloc()或相關(guān)函數(shù)分配(且未釋放)的一個內(nèi)存塊的指針 // 返回ptr指向的已分配內(nèi)存塊的可用字節(jié)數(shù)。若ptr為NULL,則返回0。 size_t malloc_usable_size (void *ptr);由于對齊或最小長度的要求,該函數(shù)返回的值可能大于內(nèi)存分配時申請空間的大小(超過的字節(jié)數(shù)取決于底層實現(xiàn))。雖然訪問超出的字節(jié)范圍不會有什么問題,但這不是好的編程習(xí)慣。
2)malloc_trim():調(diào)用sbrk()釋放堆頂部的空閑內(nèi)存。
// 內(nèi)存被釋放給系統(tǒng)時返回1,不可能釋放任何內(nèi)存時返回0 void malloc_trim(size_t pad);參數(shù)pad指定收縮時堆頂部保留的空閑空間大小。若為0,則堆頂部只保留最小限度的內(nèi)存量(一個頁甚至更少);若非0,則保留堆頂部末尾的一些空間,這樣后續(xù)的內(nèi)存分配操作不需要使用sbrk()擴展堆。
在某些情況下,該函數(shù)被free()自動調(diào)用;該函數(shù)只能釋放堆頂部的空閑內(nèi)存。
3)malloc_stats():在標準出錯上打印內(nèi)存分配統(tǒng)計情況(statistics)。
void malloc_stats(void);對每個arena,該函數(shù)打印系統(tǒng)分配(映射)的內(nèi)存總量和實際分配操作消耗的內(nèi)存總量。如下面的例子:
char *p = (char *)malloc(700); char *q = (char *)malloc(5205205);malloc_stats();相應(yīng)的輸出:
~/programming$ ./a.out Arena 0: system bytes = 135168 in use bytes = 704 Total (incl. mmap): system bytes = 5341184 in use bytes = 5206720 max mmap regions = 1 max mmap bytes = 5206016每個arena(allocation area)是系統(tǒng)內(nèi)部使用brk()或mmap()分配的一個大的內(nèi)存區(qū)域。它用自己的互斥鎖管理。
為了可擴展地處理多線程應(yīng)用的內(nèi)存分配操作,當(dāng)檢測到互斥鎖競爭時,glibc創(chuàng)建額外的arena。
4)malloc調(diào)試變量:__malloc_hook、__malloc_initialize_hook、__free_hook和__realloc_hook等。
void *(*__malloc_hook)(size_t size, const void *caller); void (*__malloc_initialize_hook)(void); void (*__free_hook)(void *ptr, const void *caller); void *(*__realloc_hook)(void *ptr, size_t size, const void *caller);__malloc_hook、__free_hook和__realloc_hook等指向的函數(shù)分別與malloc()、free()和realloc()等函數(shù)有著相同的原型,除了最后多了一個caller參數(shù)。
通過指定適當(dāng)?shù)膆ook函數(shù),GNU C庫允許你修改malloc()、realloc()和free()的行為。一個用處是,你可以使用這些hook幫助你調(diào)試使用了動態(tài)內(nèi)存分配的程序。例:
void *(*old_malloc_hook) (size_t, const void *caller);void *my_malloc_hook(size_t size, const void *caller) { // 沒有這一句的話會遞歸調(diào)用本函數(shù)__malloc_hook = old_malloc_hook;void *result = malloc(size);printf ("malloc (%u) returns %p\n", (unsigned int) size, result);// 保證后續(xù)的malloc()仍可以使用自定義的hook__malloc_hook = my_malloc_hook;return result; }void malloc_hook_init() {old_malloc_hook = __malloc_hook;__malloc_hook = my_malloc_hook; }int main() {malloc_hook_init();malloc(123);return 0; } 這些hook函數(shù)在多線程程序中使用是不安全的,并且它們現(xiàn)在已被棄用。
5)malloc_info():將malloc狀態(tài)導(dǎo)出到一個流中。
// 成功時返回0,出錯時返回-1并相應(yīng)地設(shè)置errno int malloc_info(int options, FILE *fp);
該函數(shù)導(dǎo)出一個描述調(diào)用者當(dāng)前內(nèi)存分配狀態(tài)的XML字符串,該字符串包含所有arena的信息。輸出格式如下:
<malloc version="1"><heap nr="0"><sizes>...</sizes><total type="fast" count="0" size="0"/><total type="rest" count="0" size="0"/><system type="current" size="1081344"/><system type="max" size="1081344"/><aspace type="total" size="1081344"/><aspace type="mprotect" size="1081344"/></heap><heap nr="1">...</heap><heap nr="2">...</heap>... </malloc>?
參考資料:
《深入Linux內(nèi)核架構(gòu)》
http://blog.jobbole.com/75656/
http://blog.csdn.net/amwihihc/article/details/7481656
http://blog.csdn.net/sgbfblog/article/details/7772153
?
?
不斷學(xué)習(xí)中。。。
轉(zhuǎn)載于:https://www.cnblogs.com/hanerfan/p/4545370.html
總結(jié)
- 上一篇: 函数 - 理解参数
- 下一篇: H.264(MPEG-4 AVC)级别(