在实现业务代码的时候,总不可避免地要动态申请内存。在C语言中,没有自动回收内存的机制,所以必须手动释放内存。如果没有正确释放内存,就会出现内存泄露。如果是在服务器端的程序出现内存泄露,那必定会导致服务不稳定,长期运行总会耗尽内存而导致无法新申请出内存,影响业务。
在C语言中,申请内存调用的是alloc类函数(之所以说类,是因为有malloc
calloc
realloc
三种),释放内存使用free,这些个函数都定义在stdlib.h
中。简单说一下alloc函数之间的区别,网上也有很多资料。
1 2 3 4
| void* malloc (size_t size); void* calloc (size_t num, size_t size); void* realloc (void* ptr, size_t size);
|
问题1:申请的内存是否需要初始化?
这个要根据具体的业务逻辑来分析。理论上来说,保证每一块内存都初始化是最保险的,但是会涉及性能的开销。因为调用memset
也是很消耗资源的。一般的客户端软件对性能不敏感,但是在服务器上就不一样了。例如通信基站上的程序,对性能异常的敏感,有时候多1ms的延迟都会导致出错。如果对于大片的内存,全部都初始化太浪费性能。
首先要分析,这片内存的使用场景:
- 如果申请出内存后的第一步操作就是覆写内存,那大可不必初始化。
- 如果申请的内存有可能先被读,那就必须初始化保证逻辑的正确。
不需要初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
#include <stdio.h>
char * copyString(char str[]) { int len = strlen(str) + 1; char *ret = malloc(len * sizeof(char)); strcpy(ret, str); return ret; }
int main () { char str[] = "hello world"; char *copy = copyString(str); return 0; }
|
需要初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
#define NOT_RCV 0 #define RCV 1
bool checkIsRcv(char *packetStatus, int index) { return packetStatus[index] == RCV }
void setPacketFlag((char *packetStatus, int index, char flag) { packetStatus[index] = flag; }
int main () { int n; sacnf("%d", &n); char *packetStatus = malloc(n * sizeof(char)); memset(status, 0, n); }
|
问题2:如何正确释放内存?
要做到完全避免内存泄露,在大型的C项目上是个难题,特别是业务逻辑很复杂时。这里只说一下简单的场景:
free
一个指针前,需要注意释放这个指针指向的结构体内部的指针指向的内存
1 2 3 4 5 6 7 8 9 10 11
| struct node { struct node *next; void *value }
void freeNode(struct node *p) { free(p->value); free(p); }
|
但是这样也还是不安全,因为value是void *
类型指针,可能value还是指向一个结构体,里面也有动态申请的内存。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| struct myArray //一个动态长度的int数组 { int size; int *array; }
struct node *p = malloc(sizeof(struct node)); p->value = (void *)malloc(sizeof(struct myArray));
struct myArrayp *q = (struct myArrayp *)p->value; q->array = malloc(q->size * sizeof(int));
void freeArray(struct myArray *p) { free(p->array); free(p); } void freeNode(struct node *p) { freeArray(p->value); free(p); }
|
其实解决问题的核心还是在于封装,某种类型的结构体,需要封装一个创建函数,和释放函数。保证释放函数可以把创建函数申请的内存和代码运行期间申请的内存全部释放掉。