动态内存分配
动态分配内存的意义
定义数组的长度的时候,必须指定长度,这是在编译期间就要确定的。
当我们无法在编译期间确定到底需要多大的内存块,此时就无法定义数组的大小:
- 实际使用的元素数量超过了声明的长度,程序无法处理。
- 如果程序实际使用的元素数量较少,巨型数组会造成内存空间浪费。
此时就需要在运行的时候根据实际的情况(比如根据输入的数据的大小),来动态的申请内存空间,然后让指针指向这块新申请的内存。
malloc和free
malloc和free是库函数,不是系统调用
#include "stdlib.h"
void * malloc(size_t size);
void free(void* pointer);
关于malloc
- malloc所分配的是一块连续的内存,参数size是所分配的内存字节数。
- malloc的返回值是void* ,具体使用的时候需要做强制类型转换。
- 当请求的动态内存无法满足的时候,malloc返回NULL,对每个从malloc返回指针都进行检查,确保它不是NULLL。
- malloc申请的动态内存中的数据是随机值,不会被初始化为0。
- malloc 实际分配的内存可能有会比请求的多。
关于free
- free的参数要么是NULL,要么是从malloc、
calloc
、realloc
返回的值。 - free用于将申请的动态内存归还给系统。
- 当 free 的参数为 NULL 时,函数直接返回。
calloc与realloc
#include "stdlib.h"
void* `calloc`(size_t num,size_t size);
void* `realloc`(void* pointer,size_t new_size);
关于calloc
calloc
函数的参数包含了所需元素的数量以及每个元素的字节数,根据这些值可以计算出一共所需的内存大小。calloc
会将申请的内存空间初始化为0。- 如果指向把值存储到数组中,这种操作会浪费一定的时间。
关于realloc
realloc
用于修改原先已经分配的内存块大小。- 当
realloc
的第一个参数pointer为NULL时,realloc
相当于malloc。 realloc
用于缩小一个内存块时,该内存块的尾部的部分将会被拿掉。realloc
用于扩大内存块时,原有内容依然保留,新增加的内存块添加到原先内存块的后面,新内存并未以任何方式初始化。- 如果原先的内存块无法改变大小,
realloc
将会分配一个正确大小的内存,并把原有的内容拷贝到新的内存块上,因此realloc
之后就不能使用指向旧内存的指针,而应该使用realloc
返回的指针。
常见的动态内存分配错误
对NULL指针解引用操作
int main(void)
{
int *p = (int *)malloc(sizeof(int));
*p = 20; //这里没有判断返回值
//如果返回值为NULL则会出现错误
free(p);
system("pause");
return 0;
}
对动态开辟的空间越界访问
int main(void)
{
int *p = (int *)malloc(10 * sizeof(int));
if (p == NULL)
{
exit(EXIT_FAILURE);
}
int i = 0;
for (i = 0; i <= 10; i++) // 访问越界
{
p[i] = i;
}
free(p);
p = NULL;
system("pause");
return 0;
}
对非动态开辟的内存使用free释放
int main(void)
{
int a = 10;
int *p = &a;
free(p); // p不是动态申请的内存
system("pause");
return 0;
}
使用free释放一块动态开辟内存的一部分
int main(void)
{
int *p = (int *)malloc(10 * sizeof(int));
if (p == NULL)
{
exit(EXIT_FAILURE);
}
p++;
free(p); // 释放部分动态申请的内存
system("pause");
return 0;
}
对同一块内存多次释放
int main(void)
{
int *p = (int *)malloc(sizeof(int));
if (p == NULL)
{
exit(EXIT_FAILURE);
}
free(p);
free(p); // 第二次释放
system("pause");
return 0;
}
动态开辟的内存忘记释放(内存泄漏)
int main(void)
{
int *p = (int *)malloc(sizeof(int) * 10);
if (p == NULL)
{
exit(EXIT_FAILURE);
}
system("pause");
return 0;
}
编写内存泄漏检查模块
头文件 mleak.h
#pragma once
#include "stdio.h"
#include "malloc.h"
#define MALLOC(n) mallocEx(n,__FILE__,__LINE__)
#define FREE(p) freeEx(p)
void *mallocEx(size_t n, const char *file, const int line);
void freeEx(void *p);
void PRINT_LEAK_INFO();
实现 mleak.c
#include "mleak.h"
#define MAXSIZE 256
typedef struct
{
void * pointer;
int size;
const char* file;
int line;
}MallocItem;
static MallocItem g_record[MAXSIZE];
void *mallocEx(size_t n, const char *file, const int line)
{
void * p = malloc(n);
int i = 0;
if (p != NULL)
{
for (i = 0; i < MAXSIZE; i++)
{
if (g_record[i].pointer == NULL)
{
g_record[i].pointer = p;
g_record[i].size = n;
g_record[i].file = file;
g_record[i].line = line;
break;
}
}
}
return p;
}
void freeEx(void *p)
{
if (p != NULL)
{
int i = 0;
for (i = 0; i < MAXSIZE; i++)
{
if (g_record[i].pointer == p)
{
g_record[i].pointer = NULL;
g_record[i].size = 0;
g_record[i].file = NULL;
g_record[i].line = 0;
break;
}
}
}
}
void PRINT_LEAK_INFO()
{ int i = 0;
printf("Potenital Memory Leak Info:\n");
for (i=0; i < MAXSIZE; i++)
{
if (g_record[i].pointer != NULL)
{
printf("Address:%p, size:%d, Location:%s:%d\n",
g_record[i].pointer,
g_record[i].size,
g_record[i].file,
g_record[i].line);
}
}
}
测试
#include "stdio.h"
#include "stdlib.h"
#include "mleak.h"
void f()
{
MALLOC(100);
}
int main()
{
int* p = (int*)MALLOC(3 * sizeof(int));
f();
p[0] = 1;
p[1] = 2;
p[2] = 3;
FREE(p);
PRINT_LEAK_INFO();
return 0;
}
结果: