一.为什么要有动态内存分配 

动态内存管理会在c\c++中都会大量使用,特别是在实现数据结构的时候

1.1向内存申请空间的两种形式:

******在栈空间上申请空间(第一种:空间大小不可变):

	int a = 0;            //向栈空间申请4个字节大小空间

	char b = 'a';         //向栈空间申请1个字节空间

	char arr[10] ={ 0 }; //向栈空间连续申请10个char类型的大小空间(10个字节)

	int s[10] = { 0 };    //向栈空间连续申请10个int类型大小的空间(40个字节)

----特点:申请时必须指明长度,一旦申请成功,空间大小不能改变。

*****在堆区上申请空间(第二种:空间大小可以改变):

为了引入第二种申请方式,我们先来看一个问题。

问题:描述一个班级学生的数学成绩?

按照上面的申请方式,我们可以在栈空间上申请一块空间来存放这个班学生的数学成绩:int math[50]={0};但是如果这一个班的学生不是50个,小于50个就会浪费空间;超过50个就会放不下(会发生越界访问)。但是如果我们在堆空间中申请一块动态空间,当学生人数是60个,我们就可以申请60个整形大小的空间来存放这一个班的数学成绩,这样不发生越届访问,也不会浪费空间,这也就是动态空间的好处。

二.动态内存开辟的函数:malloc

函数原型:void* malloc(size_t size);

1.函数返回类型是void*(指针类型);void*可以指向任何类型的指针;但是void类型指针不是进行解引用,必须进行强制类型转换才能进行解引用;

2.size是在堆区申请的空间大小,单位是字节;

例如:int *p=(int*)malloc(sizeof(int));

指在堆空间申请大小为4个字节的空间,并把首地址强制类型转换成整形(int)指针,然后赋给整形指针变量p;

3.头文件:

#include<stdlib.h>

注意事项:

1.申请(开辟)成功:会返回指向开辟空间的指针;

2.申请(开辟)失败:会返回一个NULL类型的指针,所以一定要对malloc的返回值进行检查;

INT_MAX代表一个很大的数;

3.返回值的类型为void*,所以malloc函数只管开辟空间,但是不知道空间的类型;具体在使用的时候由使用者决定(通过强制类型转换,如上面例子);

4.当size为0时;malloc的行为是未被定义的,大小取决于编译器,可能申请成功,可能申请失败;

三.free函数

c语言中,提供一个一个函数free,用来释放动态内存空间,即申请了动态内存空间,就要去释放动态内存空间。

函数原型:void free(void * str);

void free(void* str);//str是要释放动态内存的起始地址

void*函数可以接受任意类型的地址,比如:int*,char*。

注意事项:

1.如果str指向的内存不是动态的,free的行为是未被定义的。

2.如果str是NULL指针,则free什么事情都不做。

3.malloc和free的声明都包含在头文件#include<stdlib.h>中。

4.free函数只会对内存进行释放,开始的指针任然还在,最后要将他变为NULL;(p=NULL)

让我们看看下面代码:

#include<stdio.h>
#include<stdlib.h>
#include<limits.h>
int main()
{
	int* p = (int*)malloc(10* sizeof(int)); //申请一块很大的动态空间
	if (p == NULL)       //检查是否开辟成功,开辟失败,p为NULL
	{
		perror("malloc");
		return 1;       //非正常退出
	}
	printf("%p\n", p);
	free(p);
	printf("%p\n", p);
	p = NULL;
	printf("%p\n", p);
	return 0;
}

5.str是要释放内存的起始地址,下面看一段代码,判断是否正确?

#include<stdio.h>
#include<stdlib.h>
int mian()
{
	int* p = (int*)malloc(sizeof(int) * 10);
	if (p ==NULL)
	{
		perror("malloc");
		return 1;
	}
	for (int i = 0; i < 10; i++)
	{
		*p = i + 1;
		p++;
	}
	free(p);
	p = NULL;
	return 0;
}

for循环中,p每次++,最后对p进行内存释放,是不正确的,因为此时的p不指向最上面开辟的动态内存的起始地址。

四.calloc函数

函数原型:void* calloc(size_t    num,size_t   size);

声明包含于#include<stdlib.h>

• 函数的功能是为 num 个大小为 size 的元素开辟⼀块空间,并且把空间的每个字节初始化为0。

• 与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全为0。

•如果以后要对字节初始化为0,那么就使用calloc,不用初始化,就使用malloc。

五.realloc函数

函数原型:void* realloc(void* str,size_t  size);

• realloc函数的出现让动态内存管理更加灵活,可以调整malloc和calloc动态空间的大小。

• 有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时 候内存,我们⼀定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小的调整。

#include<stdio.h>
#include<stdlib.h>
int main()
{
	int* p = (int*)calloc(3, 4);   //申请3个大小为4个字节的空间
	if (p == NULL)
	{
		perror("calloc");
		return 1;
	}
	for (int i = 0; i < 3; i++)
	{
		p[i] = i + 1;
	}
	               //1 2 3 
	int* str=(int*)realloc(p, 4 * sizeof(int));
	if (str != NULL)         //对申请的地址进行检查
	{
		p=str;
		*(p+3) = 4;     //1 2 3 4 	
		for (int i = 0; i < 4; i++)
		{
			printf("%d ", *(p + i));
		}
		free(p);
		p = NULL;
	}
	return 0;
}

realloc函数调整空间的两种情况:

1.如果开始创建的空间后面有足够的空间,那就在后面新增空间。

2.如果后面空间不够,那就在堆区新找一块新的空间,返回新的起始地址。

并且会把开始的数据拷贝到新的空间里,把原来的空间自动释放。

3.如果上面两种都不行,则申请失败,返回NULL。

所以,我们使用realloc函数时,如果直接把地址传给原来的p指针,如果申请失败,那么就找不到原来的内存,这时候就会发生内存泄漏。

因此我们应该先用新创建一个新的指针,如果指针不是NULL,则再赋给p。

	int* str=(int*)realloc(p, 4 * sizeof(int));
	if (str != NULL)         //对申请的地址进行检查
	{
		p=str;
		*(p+3) = 4;     //1 2 3 4 	
		for (int i = 0; i < 4; i++)
		{
			printf("%d ", *(p + i));
		}
		free(p);
		p = NULL;
	}

•当str为NULL时,功能类似于malloc函数,因为不能新增一块空间,只能去寻找一块新的空间,并且返回起始地址。

六.常见的动态内存错误

1.对NULL进行解引用操作。

#include<stdio.h>
#include<stdlib.h>
int main()
{
	int* p = (int*)(INT_MAX);
	*p = 1;
	free(p);
	p = NULL;
	return 0;
}

没有对p指针进行检查,就对p进行解引用操作,因为如果申请失败,p可能为NULL。

解决办法:用if进行判断,或者用assert(p)进行判断。

2.对动态开辟的空间的越界访问。

3.对非动态开辟的空间进行free释放。

#include<stdio.h>
#include<stdlib.h>
int mian()
{
	int a = 0;
	int* pa = &a;
	free(pa);   //对非动态内存进行释放
	pa = NULL;
	return 0;
}

4.使用free释放动态内存的一部分。

#include<stdio.h>
#include<stdlib.h>
int mian()
{
	int* p = (int*)malloc(4 * sizeof(int));
	if (p == NULL)
	{
		perror("malloc()");
		return 1;
	}
	for (int i = 0; i < 3; i++)
	{
		*p = i + 1;
		p++;
	}
	free(p);
	p = NULL;
	return 0;
}

free(p)没用把malloc申请的动态内存空间全部释放。for循环中,p向后移了3位。不再是动态内存的起始位置。

5.对同一块动态内存进行多次释放。

#include<stdio.h>
#include<stdlib.h>
int mian()
{
	int* p = (int*)malloc(4 * sizeof(int));
	if (p == NULL)
	{
		perror("malloc()");
		return 1;
	}
	free(p);
	free(p);   //对动态内存内存进行多次释放
	return 0;
}

解决办法:如果我们可以再第一次free(p)后面把p变为NULL。那么后面的free就什么都不做。

6.动态内存空间忘记释放(内存释放)。

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
void test()
{
	int* p = (int*)malloc(3 * sizeof(int));
	if(p==NULL)
	{
		assert(p);
	}
	for (int i = 0; i < 3; i++)
	{
		*(p + i) = i + 1;
		printf("%d ", *(p + i));
	}
}
int mian()
{
	test();
	return 0;
}

该代码中,p创建的是局部变量,当test函数运行结束时,p就会销毁,但是在test()函数中,没有进行动态内存释放。如果我们再想在main函数中对动态内存释放,我们是不能找到这一块空间的,这个时候就会放生内存泄漏,如果内存释放过多,内存就会被耗干。

*如果发生内存释放,如果有内存泄漏,当程序结束的时候,操作系统会进行回收。

*解决办法:malloc函数与free函数成对出现。

malloc函数成对出现也不一没有内存泄漏,因为可能虽然成对出现,但是有一些语句没有执行,被一些语句提前返回了。

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

int mian()
{
	int* p = (int*)malloc(5 * sizeof(int));
	if (p == NULL)
	{
		perror("malloc()");
		return 1;
	}
	*p = 0;
	if (*p == 0)
	{
		return 0;
	}
	free(p);
	p = NULL;
	return 0;
}

七.动态内存经典笔试题分析

题目一:(传值没有改变实参内容)

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void GetMemory(char* p)
{
	p = (char*)malloc(100);
}
void Test(void)
{
	char* str = NULL;
	GetMemory(str);
	strcpy(str, "hello world");
	printf("%s",str);
}
int mian()
{
	Test();
}

1.p与str之间是传值,p是str的一份临时拷贝,malloc函数创建空间的首地址给了p,但是p的改变不会影响str指针,str指针任然还是NULL,不能对NULL进行解引用操作,strcpy函数不能实现。

2.Get Memory函数结束后,p指针被销毁,但是malloc创建的空间没有被释放,使用会放生内存泄漏。

解决办法:

进行传指调用;

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void GetMemory(char** p)
{
	char* pa = (char*)malloc(100);
	if (pa == NULL)
	{
		perror("malloc()");
	}
	*p=pa;
}
void Test(void)
{
	char* str = NULL;
	GetMemory(&str);
	strcpy(str, "hello world");
	printf("%s",str);
    free(str);
    str=NULL:
}
int mian()
{
	Test();
}

问题二:(局部变量已经销毁)

#include<stdlib.h>
#include<string.h>
char* GetMemory(void)
{
	char p[] = "hello world";
	return p;
}
void Test(void)
{
	char* str = NULL;
	str = GetMemory();
	printf("%s",str);
}
int mian()
{
	Test();
}

这题可以看出GetMemory函数的返回值是char*,是一个指针,str也确确实实拿到了p

的地址。但是p数组是在GetMemey函数中创建的,出了这个函数,p数组就会返回给操作系统,虽然str拿到了p的地址,但是str为野指针,我们对str指向的空间没有访问权限。

问题三:(内存泄漏)

#include<stdlib.h>
#include<string.h>
void GetMemory(char** p, int num)
{
	*p = (char*)malloc(num);
}
void Test(void)
{
	char* str = NULL;
	GetMemory(&str, 100);
	strcpy(str, "hello");
	printf(str);
}
int mian()
{
	Test();
}

没有对动态内存进行free释放(内存泄漏)。

问题四:(对释放完成的空间,没有把指针置为NULL)

#include<stdlib.h>
#include<string.h>
void Test(void)
{
	char* str = (char*)malloc(100);
	strcpy(str, "hello");
	free(str);
	if (str != NULL)
	{
		strcpy(str, "world");
		printf(str);
	}
}
int mian()
{
	Test();
}

问题在于,虽然进行了动态内存释放了,但是没有只进行free释放,没有把str置为NULL。

后面的if语句,对NULL进行了解引用。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐