动态内存分配

动态分配内存的意义

定义数组的长度的时候,必须指定长度,这是在编译期间就要确定的。

当我们无法在编译期间确定到底需要多大的内存块,此时就无法定义数组的大小:

  • 实际使用的元素数量超过了声明的长度,程序无法处理。
  • 如果程序实际使用的元素数量较少,巨型数组会造成内存空间浪费。

此时就需要在运行的时候根据实际的情况(比如根据输入的数据的大小),来动态的申请内存空间,然后让指针指向这块新申请的内存。

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、callocrealloc返回的值。
  • 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;
}

结果:

posted @ 2019-09-15 10:36  youngliu91  阅读(1352)  评论(0编辑  收藏  举报