c语言内存无法释放

这两天用C语言做一个大数据处理的程序,对于一个处理大数据的程序来说除了考虑处理速度之处还有一个要考虑的就是内存管理(当然不是说操作系统的内存分配),因为数据量本身就非常大很多时候是没有额外的内存来让程序浪费的,所以我们要做的就是及时回收内存。

但就在回收内存的时候我遇到一个问题:虽然我对每一个子串处理完了就将为它分配的内存释放,但是程序所占的内存还是越来越多,我首先考虑到是不是那里有分配 的空间忘了释放,检查一遍没有发现问题。如果这样那没有释放掉内存的原因可能就出现在释放内存的机制上了,我做了一个测试,代码如下:

Case #1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct cut
{
char a[1024*100];
struct cut *next;
}cut;
typedef struct node
{
char a[1024*100];
struct node *next;
}node;
cut *cut_first,*cut_now;
node *node_first,*node_now;
int main()
{
node *a;
cut *b;
int i;
for(i=0;i<10;i++){
a=(node *)malloc(sizeof(node));
a->next=NULL;
if(node_first==NULL) node_first=a;
else node_now->next=a;
node_now=a;
b=(cut *)malloc(sizeof(cut));
b->next=NULL;
if(cut_first==NULL) cut_first=b;
else cut_now->next=b;
cut_now=b;
}
printf("malloc all \n");
scanf("%d",&i);
printf("start free node\n");
while(node_first!=NULL)
{
node_now=node_first->next;
free(node_first);
node_first=node_now;
}
printf("have free node\n");
scanf("%d",&i);
printf("start free cut\n");
while(cut_first!=NULL)
{
cut_now=cut_first->next;
free(cut_first);
cut_first=cut_now;
}
printf("have free cut\n");
scanf("%d",&i);
return 0;
}

实验结果是内存直到程序结束才释放,free函数一点作用也没有发挥。因为我在结cut和node两个结构体分配内存时是交叉分配的(同时分配),我猜想问题就出在这是,因为同时分配导致分配给它们的内存空间也是相邻的,在释放的时候虽然将其中一个块结释放了但相邻的两块却没有释放,所以无法形成一个大的内存块(碎片过多)分配结其它需要内存的程序使用,所以这内存一直就被浪费,直到程序结束。(这只是我的一个猜想,我使用的是windows7 vc++6.0 我并不知道windows内存分配的算法,不过好像在linux也会出现这种问题,欢迎指正)。

如果我不交叉分配,而是一次这一个结构体分配足够内存,释放内存的时候就能将空间释放掉。

Case #2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct cut
{
char a[1024*100];
struct cut *next;
}cut;
typedef struct node
{
char a[1024*100];
struct node *next;
}node;
cut *cut_first,*cut_now;
node *node_first,*node_now;
int main()
{
node *a;
cut *b;
int i;
for(i=0;i<10;i++){
a=(node *)malloc(sizeof(node));
a->next=NULL;
if(node_first==NULL) node_first=a;
else node_now->next=a;
node_now=a;
}
for(i=0;i<10;i++){
b=(cut *)malloc(sizeof(cut));
b->next=NULL;
if(cut_first==NULL) cut_first=b;
else cut_now->next=b;
cut_now=b;
}
printf("malloc all \n");
scanf("%d",&i);
printf("start free node\n");
while(node_first!=NULL)
{
node_now=node_first->next;
free(node_first);
node_first=node_now;
}
printf("have free node\n");
scanf("%d",&i);
printf("start free cut\n");
i=0;
while(cut_first!=NULL)
{
cut_now=cut_first->next;
free(cut_first);
cut_first=cut_now;
i++;
}
printf("have free cut %d \n",i);
scanf("%d",&i);
return 0;
}

新更新:

这篇博客是我写于好几年前, 最近正在研究glibc中内存管理方面的知识,顺便更新一下这篇博客。首先指出两种代码运行情况。

  1. 在windows 10 中 vs 2015 的环境下, 上面两种代码都不会出现内存无法释放(说明windows内存分配有变化)。

  2. 在 linux下 gcc version 5.4.0, 上面两种代码都会出现内存无法释放。

现在开始分析为什么在linux下内存无法释放(不对windows作讨论):

首先我们从代码Case #3入手:

Case #3:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
typedef struct node
{
char a[1024*100];
struct node *next;
}node;
int main()
{
node *node_first = NULL, *node_now;
node *a;
int i;
char s_cmd[100];
sprintf(s_cmd, "pmap -d %lu | grep mapped", getpid());
//打印堆顶地址
printf("before malloc, the top of heap is 0x%lx\n", sbrk(0));
for(i=0;i<10;i++){
a=(node *)malloc(sizeof(node));
a->next=NULL;
if(node_first==NULL) node_first=a;
else node_now->next=a;
node_now=a;
//打印node节点的地址和堆顶地址
printf("address of a is 0x%lx ", a);
printf("top of heap after malloc is 0x%lx\n", sbrk(0));
}
a=(node *)malloc(sizeof(node)); //在这里分配一个节点是为了占用堆顶部,方便我们查看glibc内存管理方式
printf("malloc end\n");
system(s_cmd); //在这里用pmap 命令查看进程占用的内存
printf("before free, the top of heap is 0x%lx\n", sbrk(0)); //打印node节点的地址和堆顶地址
while(node_first!=NULL) //释放链表
{
node_now=node_first->next;
free(node_first);
node_first=node_now;
}
printf("after free, the top of heap is 0x%lx\n", sbrk(0));
system(s_cmd);//在这里用pmap 命令查看进程占用的内存, 链表释放后,程序占有的内存没变
free(a);
system(s_cmd);//在这里用pmap 命令查看进程占用的内存, 释放掉a后, 链表占有的内存才被释放掉。
return 0;
}

代码一共分为两个部分: 首先我们动态申请一个链表, 然后释放这个链表, 并同时查看这个进程占用的内存。这段代码运行结果如下:

结果

从运行结果可以看出:

  1. 链表所有节点都是在堆上分配的(因为从运行结果可以看出链表各个节点的地址小于堆顶的地址)。

  2. 在链表释放前后进程占有的内存没有变化, 堆顶地址也没有变化(看第一个红框和第二个)。

  3. 在节点a释放后链表占用的内存归还给了操作系统(第三个红框显示,进程占用的内存有降低)。

为什么在释放掉链表node_first后进程占有的内存没有变化呢?

因为malloc采用了两中不同的方式来处理内存申请:

  1. 若分配内存小于 128k ,调用 sbrk() ,将堆顶指针向高地址移动,获得新的虚存空间。

  2. 若分配内存大于 128k ,调用 mmap() ,在文件映射区域中分配匿名虚存空间。

用mmap()分配的内存释放后会立即归还给系统, 然而采用sbrk()分配的内存调用free()后并不一定会马上规划给系统。具体细节请看参考文献[2]。

因为链表每个节点的size小于128K,所以malloc在堆上为链表分配内存,于是在a释放前链表的占用内存没有被释放。 现在知道了为什么Case 1和2 在链表free后, 进程占有的内存不变的原因了。

参考

[1] 十问 Linux 虚拟内存管理 (glibc)
[2] 频繁分配释放内存导致的性能问题的分析
[3] ptmalloc