动态内存管理(超级详讲)
一.为什么要有动态内存分配二.动态内存开辟的函数:malloc三.free函数四.calloc函数五.realloc函数六.常见的动态内存错误七.动态内存经典笔试题分析
一.为什么要有动态内存分配
动态内存管理会在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进行了解引用。
更多推荐
所有评论(0)