一.为什么要进行动态内存管理
我们现在已有的内存开辟方式有:
int a = 10;//在栈上开辟四个字节的空间
int arr[10] = { 0 };//在栈上开辟40个字节的连续空间
但是上述申请内存空间的方式有两个特点:
- 空间开辟的大小是固定的:
- 数组在申请空间的时候,必须说明其大小,一旦申请后,大小便不可再改变。
但是我们在日常写代码过程中不可能只遇到这种情况。有时候开辟的空间的大小在程序运行时我们才能知道,那以上两种方式开辟空间的方式就不再满足我们的需求了。
所以,C语言提出了动态内存管理,让程序员可以自己开辟所需大小的空间,让空间的使用更加灵活。
二.malloc和free
malloc和free是动态内存管理的两个重要函数,我们要利用它们来进行内存的申请和释放。
2.1malloc函数
malloc函数是C语言提供的一个动态内存开辟的函数,它会开辟一个连续的内存空间。它的参数是所需要开辟内存空间的大小,单位是字节,返回值是一个void*类型的指针,指向该开辟的内存的首地址。malloc的使用有以下几个特点:
- 如果内存开辟成功,返回已开辟好的空间的地址;
- 如果开辟失败,则返回NULL指针,所以malloc的返回值一定要检查;
- 该函数的返回值是void*,所以该函数并不知道要开辟空间的类型,由程序员设置;
- 如果size为0,则该行为是未定义的,结果取决于编译器;
- 使用malloc函数需要包含头文件。
我们看到,利用该函数确实开辟了一块40个字节的连续内存空间。
2.2free函数
上面,我们利用malloc函数开辟了内存空间,那我们开辟空间是为了使用,那我们使用完之后最好把该空间再还给操作系统,这样才不会造成内存空间的泄露服务器托管。
C语言也提供了free函数,用来释放和回收动态开辟的内存空间。
free函数的参数是一个指针,指向的是一块由malloc、calloc或者realloc开辟的动态的内存空间。而该函数无返回值。该函数有以下注意事项:
- 该函数释放的内存空间必须是由malloc、calloc或者realloc开辟的动态内存空间,否则将会是未定义的行为;
- 如果参数为NULL,则free函数不进行任何操作;
- 该函数不会修改其自身的值,他还是指向那块空间,只是没有了该空间的使用权;
- 传给free的参数一定得是要被释放的内存的起始位置;
- free函数的使用得包含头文件。
我们看,当我们使用free之后,该动态内存空间就被释放了,还给了操作系统。
动态申请的空间已经被释放,那参数的内容是否被修改了呢?
我们看到,该指针依旧指向已经被释放的那块空间,只是没有了该空间的使用权,这时,该指针就成了野指针。为了避免野指针对程序的危害,我们应该主动将释放后的指针置为空指针。
我们上面还提到,对于malloc申请的空间要进行检查,只有申请成功才会返回申请空间的起始地服务器托管址,否则返回NULL,为了避免对NULL的操作,所以我们应该在申请完后对其进行检查。
修改后的代码为:
#include
#include
int main()
{
//开辟了一个40个字节的内存大小
int* p = (int*)malloc(sizeof(int) * 10);
printf("%pn", p);
//对所申请的空间进行检查,如果为NULL,则申请失败,终止程序
if (p == NULL)
{
perror("malloc");
return 1;
}
//利用该空间存放1~10
int i = 0;
for (i = 0; i
三.calloc和realloc
在动态申请内存的过程中,除了malloc和free这两个函数,calloc和realloc也是两个非常重要的申请动态空间的函数。
3.1calloc函数
- calloc的功能是为num个大小为size的元素开辟一块连续的动态内存,并且将空间的每个字节都初始化为0;
- calloc函数与malloc函数的区别只是在calloc会将申请的空间的每一个字节初始化为。
我们调试内存窗口发现,calloc确实会将开辟的空间的每一个字节赋值为0。
如果我们对申请的内存空间有初始化的要求,那么用calloc就可以很方便的实现。
3.2realloc函数
realloc函数的存在使得动态内存的管理更加灵活。
有时候我们发现先前开辟的动态内存太小了,有时候又觉得太大了,那么为了更加合理的使用内存,我们肯定会对内存的大小进行灵活地调整。那么realloc函数就可以对动态内存空间进行大小的调整。
该函数的功能就是将ptr所对应的内存空间修改成size大小,单位是字节。使用该函数有以下几点需要注意:
- ptr是要调整的大小的内存地址;
- size是调整之后的内存大小;
- 该函数返回调整之后内存的起始地址。
而realloc对内存大小的修改有三种情况:
3.2.1情况一:
3.2.2情况二:
3.2.3情况三:
开辟空间失败,返回NULL。
3.2.4realloc函数使用的注意事项
int main()
{
int* p = (int*)malloc(sizeof(int) * 5);
//……
p = realloc(p, sizeof(int) * 10);
return 0;
}
我们是否可以直接用p接收新开辟的内存空间呢?
如果realloc开辟空间失败了呢?我们不仅没有得到新的内存空间,还丢失了原先p所控制的20个字节。所以,我们不可以直接用p接收。我们可以先创建一个临时变量来接收新空间的地址,如果该空间成功开辟,我们再将新空间的地址赋给p。
#include
#include
int main()
{
int* p = (int*)malloc(sizeof(int) * 5);
if (p == NULL)
{
perror("malloc");
return 1;
}
int *ptr = realloc(p, sizeof(int) * 10);
if (ptr != NULL)//开辟成功
{
p = ptr;
ptr = NULL;
//...
}
else//开辟失败,可以继续使用原来的空间
{
//...
}
return 0;
}
3.2.5realloc的特殊用法
如果传给realloc函数的第一个参数为NULL,则realloc的作用就与malloc相同,会开辟一个size个字节的内存空间。
int main()
{
int* p = realloc(NULL, sizeof(int) * 5);
//上式 == malloc(sizeof(int)*5);
return 0;
}
四.常见的动态内存的错误
4.1对NULL的解引用操作
我们在使用malloc、calloc、realloc函数动态开辟空间的时候很少遇到开辟失败的情况,但也不是没有。
#include
#include
int main()
{
int* p = (int*)malloc(10*INT_MAX);
*p = 20;
free(p);
return 0;
}
我们调试上面代码发现,出现了错误,说p是nullptr,即p是空指针。我们对空指针进行解引用操作肯定会报错,所以我们在使用动态内存开辟函数的时候,要进行检查!!!
我们对该代码增加检查代码后,发现打印了错误信息,说明此时p指针为NULL,即内存空间开辟失败。此时就不能对其进行解引用操作了。
4.2对动态开辟空间的越界访问
我们知道,malloc、calloc和realloc开辟的是一段连续的内存,我们完全可以将其看成是一个数组。
//4.2对动态开辟空间的越界访问
#include
#include
int main()
{
int* p = (int*)malloc(sizeof(int) * 5);
if (p == NULL)
{
perror("malloc");
return 1;
}
int i = 0;
for (i = 0; i
对于该代码来说,利用malloc开辟的内存空间就相当于创建了一个大小为5的整型数组,而我们在对其进行赋值操作的时候,发生了越界访问,此时访问到不是自己的空间数据会导致程序崩溃。
运行结果为:
4.3对非动态开辟内存使用free
//4.3对非动态开辟内存使用free
#include
#include
int main()
{
int n = 20;
int* p = &n;
free(p);
p = NULL;
return 0;
}
我们在介绍free函数的时候就强调,free函数所释放的空间必须是动态开辟的内存空间,如果释放其他的空间是未定义的行为。
4.4使用free释放动态开辟内存的一部分
//4.4使用free释放动态开辟内存的一部分
#include
#include
void test()
{
int* p = (int*)malloc(100);
p++;
free(p);//p不再指向动态内存的起始位置
}
int main()
{
test();
return 0;
}
我们也在介绍free函数的时候强调过,free函数参数必须是动态内存空间的起始位置。在该函数中,p已经不是动态内存的起始位置,对其进行free操作也是未定义的。
4.5对同一块动态内存多次释放
//4.5对同一块动态内存多次释放
#include
#include
void test()
{
int* p = (int*)malloc(100);
free(p);
free(p);//重复释放
}
int main()
{
test();
return 0;
}
我们对动态内存进行free操作后,若不将其置为NULL,其就是野指针,不再表示动态内存,对其在进行free操作,同样是错误的,该行为是未定义的。
但是我们在写代码的过程中,代码量过多的话,我们会忘记我们是否已将其内存释放,所以难免会对其进行多次释放。
为了解决这个问题,我们可以在一次free之后,立马将其置为NULL,这样就算之后在对其进行释放,也不会报错,因为free对NULL是不进行任何操作的。
4.6动态开辟内存忘记释放(内存泄露)
我们在使用动态内存开辟函数的时候,使用完之后就应该将内存释放掉,还给操作系统。但是我们有可能忘记要释放内存,但是再程序结束的时候,就算我们没有进行释放,操作系统会主动释放那些动态申请的空间。
但是对于一些服务器程序来说,基本上要一直运行,如果没有进行free操作,内存就会被浪费,如此往复,内存就用被占用完。虽然你重启后内存又恢复了,但是治标不治本,运行几次内存又会被占用。这就是内存泄露。
为了避免内存泄露的问题,我们再使用完动态开辟的内存之后,一定要记得free掉,如果没法立即释放,也要告诉要使用的人,使用完记得free掉。
完!
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net