問題:
我們在寫程序的時候經常發現程序使用的內存往往比我們申請的多,為了優化程序的內存占用,攪盡腦汁想要優化內存占用,可是發現自己的代碼也無從優化了,怎么辦?現在我們把我們的焦點放到malloc上,畢竟我們向系統申請的內存都是通過它完成了,不了解他,也就不能徹底的優化內存占用。
來個小例子
2 #include<iostream>
3 using namespace std;
4 int main( int argc, char *argv[])
5 {
6 int malloc_size = atoi(argv[ 1 ]);
7 char * malloc_char;
8 for (size_t i = 0 ; i < 1024 * 1024 ; ++i) {
9 malloc_char = new char [malloc_size];
10 }
11 while ( 1 ) {} // 此時查看內存占用
12 return 0 ;
13 }
本文的測試環境為Linux 64Bit ,使用G++編譯為可執行文件后,使用不同的啟動參數啟動,使用top命令查看程序占用的內存,這里我們主要是看RES指標
RES -- Resident size (kb)
The non-swapped physical memory a task has used.
測試案例:
1.每次new 1 Byte Do 1024*1024次
./malloc_addr_vec 1
啟動程序后的內存占用
內存消耗 32MB
2.每次new 24 Byte Do 1024*1024次
./malloc_addr_vec 24
啟動程序后的內存占用
![]()
內存消耗32MB
3.每次new 25 Byte Do 1024*1024次
./malloc_addr_vec 25
啟動程序后的內存占用
內存消耗48MB
為什么我們每次new 1Byte 和每次 new 24Byte系統消耗的內存一樣呢?,為什么每次new 25Byte和 每次new 24Byte占用的內存完全不同呢?
不知道大家在寫程序的時候有沒有關注過這個問題。我一次遇到時,吐槽一句:What the fuck malloc.
原因分析:
在大多數情況下,編譯器和C庫透明地幫你處理對齊問題。POSIX 標明了通過malloc( ), calloc( ), 和 realloc( ) 返回的地址對於任何的C類型來說都是對齊的。
對齊參數(MALLOC_ALIGNMENT) 大小的設定並需滿足兩個特性
1.必須是2的冪
2.必須是(void *)的整數倍
至於為什么會要求是(void *)的整數倍,這個目前我還不太清楚,等你來發現...
根據這個原理,在32位和64位的對齊單位分別為8字節和16字節
但是這並解釋不了上面的測試結果,這是因為系統malloc分配的最小單位(MINSIZE)並不是對齊單位
為了進一步了解細節,從GNU網站中把glibc源碼下載下來,查看其 malloc.c文件

2 #define INTERNAL_SIZE_T size_t
3 #endif
4 #define SIZE_SZ (sizeof(INTERNAL_SIZE_T))
5 #ifndef MALLOC_ALIGNMENT
6 #define MALLOC_ALIGNMENT (2 * SIZE_SZ)
7 #endif
8
9
10 struct malloc_chunk {
11 INTERNAL_SIZE_T prev_size; /* Size of previous chunk (if free). */
12 INTERNAL_SIZE_T size; /* Size in bytes, including overhead. */
13 struct malloc_chunk* fd; /* double links -- used only if free. */
14 struct malloc_chunk* bk;
15 };
16
17 An allocated chunk looks like this :
18 chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
19 | Size of previous chunk, if allocated | |
20 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
21 | Size of chunk, in bytes |M|P|
22 mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
23 | User data starts here... .
24 . .
25 . (malloc_usable_size() bytes) .
26 . |
27 nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
28 | Size of chunk |
29 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
30
31
32 #define MALLOC_ALIGN_MASK (MALLOC_ALIGNMENT - 1)
33 #define MIN_CHUNK_SIZE (sizeof(struct malloc_chunk))
34 #define MINSIZE /
35 (unsigned long )(((MIN_CHUNK_SIZE+MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK))
36 /* pad request bytes into a usable size -- internal version */
37 #define request2size(req) /
38 (((req) + SIZE_SZ + MALLOC_ALIGN_MASK < MINSIZE) ? /
39 MINSIZE : /
40 ((req) + SIZE_SZ + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK)
其中request2size這個宏就是glibc的內存對齊操作,MINSIZE就是使用malloc時占用內存的最小單位。根據宏定義可推算在32位系統中MINSIZE為16字節,在64位系統中MINSIZE一般為32字節。從request2size還可以知道,如果是64位系統,申請內存為1~24字節時,系統內存消耗32字節,當申請內存為25字節時,系統內存消耗48字節。 如果是32位系統,申請內存為1~12字節時,系統內存消耗16字節,當申請內存為13字節時,系統內存消耗24字節。
一般他們的差距是一個指針大小,計算公式是
max(MINSIZE,in_use_size)
其中in_use_size=(要求大小+2*指針大小-指針大小)align to MALLOC_ALIGNMENT
(對於上面計算的由來可以參見glibc 內存池管理 ptmalloc這篇文章的第4節chuck部分以及搜一下malloc的內部實現源碼 )
為了證明這個理論的正確性,我們需要計算一次malloc到底花掉了多少內存,我們用如下代碼分別在32bit Linux和 64bit Linux上做測試
3 #include<stdlib.h>
4 int main()
5 {
6 char * p1;
7 char * p2;
8 int i= 1 ;
9 printf( " %d\n " , sizeof ( char *));
10 for (;i< 100 ;i++)
11 {
12 p1=NULL;
13 p2=NULL;
14 p1=( char *)malloc(i* sizeof ( char ));
15 p2=( char *)malloc( 1 * sizeof ( char ));
16 printf( " i=%d %d\n " ,i,(p2-p1));
17 }
18
19 getchar();
20 }
其測試結果如下:
32bit

2 Linux 32bit
3 ---------------------
4 4
5 i= 1 16
6 i= 2 16
7 i= 3 16
8 i= 4 16
9 i= 5 16
10 i= 6 16
11 i= 7 16
12 i= 8 16
13 i= 9 16
14 i= 10 16
15 i= 11 16
16 i= 12 16
17 i= 13 24
18 i= 14 24
19 i= 15 24
20 i= 16 24
21 i= 17 24
22 i= 18 24
23 i= 19 24
24 i= 20 24
25 i= 21 32
26 i= 22 32
27 i= 23 32
28 i= 24 32
29 i= 25 32
30 i= 26 32
31 i= 27 32
32 i= 28 32
33 i= 29 40
34 i= 30 40
35 i= 31 40
36 i= 32 40
37 i= 33 40
38 i= 34 40
39 i= 35 40
40 i= 36 40
41 i= 37 48
42 i= 38 48
43 i= 39 48
44 i= 40 48
45 i= 41 48
46 i= 42 48
47 i= 43 48
48 i= 44 48
49 i= 45 56
50 i= 46 56
51 i= 47 56
52 i= 48 56
53 i= 49 56
54 i= 50 56
55 i= 51 56
56 i= 52 56
57 i= 53 64
58 i= 54 64
59 i= 55 64
60 i= 56 64
61 i= 57 64
62 i= 58 64
63 i= 59 64
64 i= 60 64
65 i= 61 72
66 i= 62 72
67 i= 63 72
68 i= 64 72
69 i= 65 72
70 i= 66 72
71 i= 67 72
72 i= 68 72
73 i= 69 80
74 i= 70 80
75 i= 71 80
76 i= 72 80
77 i= 73 80
78 i= 74 80
79 i= 75 80
80 i= 76 80
81 i= 77 88
82 i= 78 88
83 i= 79 88
84 i= 80 88
85 i= 81 88
86 i= 82 88
87 i= 83 88
88 i= 84 88
89 i= 85 96
90 i= 86 96
91 i= 87 96
92 i= 88 96
93 i= 89 96
94 i= 90 96
95 i= 91 96
96 i= 92 96
97 i= 93 104
98 i= 94 104
99 i= 95 104
100 i= 96 104
101 i= 97 104
102 i= 98 104
103 i= 99 104
64bit

2 Linux 64bit
3 -------------------
4 8
5 i= 1 32
6 i= 2 32
7 i= 3 32
8 i= 4 32
9 i= 5 32
10 i= 6 32
11 i= 7 32
12 i= 8 32
13 i= 9 32
14 i= 10 32
15 i= 11 32
16 i= 12 32
17 i= 13 32
18 i= 14 32
19 i= 15 32
20 i= 16 32
21 i= 17 32
22 i= 18 32
23 i= 19 32
24 i= 20 32
25 i= 21 32
26 i= 22 32
27 i= 23 32
28 i= 24 32
29 i= 25 48
30 i= 26 48
31 i= 27 48
32 i= 28 48
33 i= 29 48
34 i= 30 48
35 i= 31 48
36 i= 32 48
37 i= 33 48
38 i= 34 48
39 i= 35 48
40 i= 36 48
41 i= 37 48
42 i= 38 48
43 i= 39 48
44 i= 40 48
45 i= 41 64
46 i= 42 64
47 i= 43 64
48 i= 44 64
49 i= 45 64
50 i= 46 64
51 i= 47 64
52 i= 48 64
53 i= 49 64
54 i= 50 64
55 i= 51 64
56 i= 52 64
57 i= 53 64
58 i= 54 64
59 i= 55 64
60 i= 56 64
61 i= 57 80
62 i= 58 80
63 i= 59 80
64 i= 60 80
65 i= 61 80
66 i= 62 80
67 i= 63 80
68 i= 64 80
69 i= 65 80
70 i= 66 80
71 i= 67 80
72 i= 68 80
73 i= 69 80
74 i= 70 80
75 i= 71 80
76 i= 72 80
77 i= 73 96
78 i= 74 96
79 i= 75 96
80 i= 76 96
81 i= 77 96
82 i= 78 96
83 i= 79 96
84 i= 80 96
85 i= 81 96
86 i= 82 96
87 i= 83 96
88 i= 84 96
89 i= 85 96
90 i= 86 96
91 i= 87 96
92 i= 88 96
93 i= 89 112
94 i= 90 112
95 i= 91 112
96 i= 92 112
97 i= 93 112
98 i= 94 112
99 i= 95 112
100 i= 96 112
101 i= 97 112
102 i= 98 112
103 i= 99 112
了解了malloc的內存對其原理后,對於程序的內存占用的優化又有了有的放矢。我們可以根據內存對齊的原則來請求內存,來制作我們的高效內存池,從而避免隱形的資源浪費.
例如,目前STL的內存池是以8Byte為對齊單位,內存池free_list大小為
free_list[0] --------> 8 byte
free_list[1] --------> 16 byte
free_list[2] --------> 24 byte
free_list[3] --------> 32 byte
... ...
free_list[15] -------> 128 byte
STL內存池在發現某個規則的內存用完了時,會進行refill,在進行chunk_alloc
例如8Byte大小的空間沒有了,調用refill,refill會將其空間准備20個,也就是20*8,當然refill做不了內存分配,他把20個8Byte的需求提交給chunk_alloc
chunk_alloc 能真正分配內存,但是它分配的時候會將內存空間*2,所以最終malloc的內存為8*20*2=320 ,32bit系統給malloc的內存為328,64bit系統給malloc的內存為336