本文结构


内存区域

在程序的执行期间经常需要动态的分配内存。
具体表现有在函数体内声明了一个变量、结构体或数组等,这样的内存是分配是由系统操作分配在上的,在执行完函数后,函数体内开头所声明的变量、结构体或数组所持有的内存空间都会被释放。所以要将函数体内的执行结果返回或反映到函数体外,一般是行不通的(不考虑全局变量)。
还有一种是由coder们调用malloc()等内存分配函数在上开辟新内存块,这些内存块会一起存在直至调用free()函数去释放。作用域广了,但也引入了潜在的内存泄露(程序卡顿)和野指针(程序crash)问题。


函数讲解

#include <stdlib.h>

void *malloc(size_t size);
void *calloc(size_t nmemb, size_t size);
void *realloc(void *ptr, size_t size);

void free(void *ptr);

引入stdlib.h头文件后可以在代码中操作内存的分配与释放。
下面来一一讲解下内存分配函数:

  • void *malloc(size_t size);

malloc()函数分配了指定大小为size的字节数,这些字节块是没有被初始化的,也就是说有可能是非\0有值的。这也正是malloc()的缺点。如果size为0,则函数返回NULL;否则返回一个指向该内存块的指针,该指针可随后被free()调用释放。
函数的返回类型是void *,即任意类型,虽然很多编译器会把malloc()返回的地址自动转换成赋值语句左边的指针类型,但在编码中,习惯在malloc()赋值前做一次强制转换。
另外,由于可能在执行内存分配时刚好内存不足,函数也会返回一个NULL,所以对所返回的指针做非空判断。如下所示:

char* p = (char*)malloc(10 * sizeof(char));
if (p == NULL) {
     // handle null error
} else {
     // go on
}
  • void *calloc(size_t nmemb, size_t size);

calloc()解决了malloc()分配的内存块无初始化的问题,每个字节都被强制赋值为\0。还有另一个特点是把内存块分配为给定了大小的数组。即第一个参数nmemb指定了分配的元素个数,第二个参数size指定了单个元素的指定大小。
刚才malloc()的示例代码可以使用calloc()的话则可修改为:

char* p = (char*)calloc(10, sizeof(char));
if (p == NULL) {
     // handle null error
} else {
     // go on
}
  • void *realloc(void *ptr, size_t size)

在程序运行中,碰到新的数据需要继续填充处理时发现原来的内存块已经不够了,则要开辟新更大的内存块以处理更多的数据,则就需要使用到realloc()函数了。
realloc()函数的第一个参数ptr表示指向老的内存块的指针,第二个参数sizt则表示新的内存块大小。
开辟出新的内存块后,会把老内存块的数据复制到新内存块中;老内存块的空间会自动被系统回收。
所以如果新开辟的内存块比较老的内存块大,则前部分是老内存块的数据,后半部分则是未初始化的数据,即内容具有不确定性;如果新开辟的内存块比老的内存块小,则相当于复制了老内存块前半部分的内存到新的内存块中去了;
函数正常执行时,返回新内存块的首地址指针;如果函数执行失败,则返回NULL
如果指定的ptrNULL,则函数的作用相当于malloc(size);
如果指定的size值为0,则相当于调用了free(ptr)去释放了原有的内存块;

  • void free(void *ptr)

释放指针ptr所指向的内存块。如果ptrNULL,则相当于什么也没做。
当对ptr执行free()操作后,要及时对ptr赋值为空,避免野指针的出现或误操作。


realloc的内存泄露陷井

说是内存泄露陷井,其实是想说一个编码不规范的现象。挺多人在使用realloc()是这么写的:

char * p = (char *)malloc(10 * size(char));
... ...
// 发现内存不够用了
p = realloc(p, 20 * size(char));

这样子的代码,如果realloc()因某种原因执行失败导致返回NULLp赋值,则失去了指向原先分配的10 * size(char)这个老内存块的指针,该老内存块再也无法通过free()函数释放了,导致内存泄露。
正确的写法应该是这样子的:

char * p = (char *)malloc(10 * size(char));
... ...
// 发现内存不够用了
char * tmp = realloc(p, 20 * size(char));
if (tmp == NULL) {
  // realloc() do failed. do some action
  return; // 这里选择return,也可以再尝试realloc一次或其它
} else {
    p = tmp;
}
// go on do something...

示例代码

以下代码演示从命令行终端中不断获取输入字符的过程。
由于一开始内存分配仅分配够存储两个字符,当执行操作时就会出现空间不足需要调用realloc()再开辟更大的空间。

其中示例代码中的函数get_string()见sodino另一篇文章【C/C++】使用getchar()实现gets()功能

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
61
62
63
64
65
66
67
68
69
70
71
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// Created by sodino on 15-3-13.
// Copyright (c) 2015年 sodino. All rights reserved.

int main/*04*/ (int argc, char ** argv) {
long cache = 2;
long max_content = 2;
long max_memory = max_content + 1; // 最后一个位置留给字符串终止符
char * pStore = calloc(max_memory, sizeof(char));
if (pStore == NULL) {
printf("calloc() failed.\n");
return EXIT_FAILURE;
}
long lenStore = 0;
long remain = max_content;

while (1) {
printf("input char or input 'exit' to exit: \n");
// 从命令行终端获取输入的字符,每次最多获取10个字符
char * pChar = get_string(10);
if (strcmp(pChar, "exit") == 0) {
break;
}

unsigned long t_len = strlen(pChar);
if (t_len > remain) {
printf("not enough memory. \n");
long increase = t_len - remain;
max_memory = max_memory + increase + cache;
char * pClone = pStore;

// 开辟更大的内存块...
pClone = realloc(pClone, max_memory * sizeof(char));
if (pClone == NULL) {
//
printf("OOM, break; pStore=%s \n", pStore);
break;
} else {
// 开拓内存成功
pStore = pClone;
remain = remain + increase + cache;
max_content = max_memory - 1;
printf("realloc(), new memory size=%lu max=%lu remain=%lu go to append string.\n", max_memory * sizeof(char), max_content, remain);
}

strlcat(pStore, pChar, max_memory);

} else {
if (strlen(pStore) == 0) {
strcat(pStore, pChar);
} else {
strlcat(pStore, pChar, max_memory);
}
}
lenStore = strlen(pStore);
remain = max_content - lenStore;
printf("lenStore=%lu max=%lu remain=%lu content:%s \n", lenStore, max_content, remain, pStore);
}
printf("end.");

// 程序终止后释放内存
free(pStore);

// 保持习惯,及时置空
pStore = NULL;

return EXIT_SUCCESS;
}

运行效果如下图:
memory_allocation_demo