C语言高阶【1】--动态内存管理【1】(可以灵活的申请和使用内存,它不香吗?)

本章概述

为什么要有动态内存分配?

  • 情况描述 :当我们创建一个变量 时,比如,int i=0 ;内存就直接分配一块空间,这个空间是固定的 。再比如,我们创建一个数组char arr[10];内存就直接分配了一块连续的空间,这个空间也是固定的 。当我们要存放的数据过大时,原先申请的空间就不能满足了,就要重新创建变量,重新申请空间了,这样做很麻烦的。又或者,当我们的数据较小时,就会有用不到的空间,就会浪费掉。总之,对于空间的使用和申请不是很灵活,很不方便
  • 动态内存的介绍 :上面咱们讲了内存申请的不灵活和不方便。动态内存就是解决这个问题的,因为有了动态内存,我们就可以想申请多少空间就可以申请多少的空间,甚至中间还能加或减少空间。这就使得我们申请内存空间很灵活了 。关于内存,咱们之前讲过了,内存划分为栈区,堆区和静态区。函数参数,局部变量,结构体和数组这些都在栈区创建和开辟空间的malloc,free,callocrealloc这些动态内存函数是在堆区全局变量和常量这些是在静态区 。所以,我们用动态内存函数是在堆区进行空间申请和创建的。
  • 动态内存的缺点 :动态内存对于内存的申请很灵活 ,这就导致了很容易出错。因为我们对于内存申请的权限变大了,对于我们管理内存的能力就要提高了,能力不够的话,很容易出错的。
  • 注意 :这些动态内存函数的头文件是<stdlib.h>.

malloc函数和free函数

关于动态内存的学习,我们把malloc,free,callocrealloc这几个内存函数掌握住就足够了。

  • malloc函数malloc函数是用来开辟内存的函数。我们先来看一下它的结构:

    void* malloc (size_t size);
    1.size_t size表示你要申请多少个字节的空间。
    2.这个函数开辟的是一块连续的空间,和数组开辟的空间是一样的,都是连续的。
    3.返回值是void的指针,当这个函数开辟好空间后,就会返回这个空间的起始地址。
    因为你要存的数据类型是不确定的,所以返回void
    的指针。所以,当我们使用时,要强制类型转换。

  • malloc函数的使用:我们开辟个4个int型的空间,进行代码展示:

javascript 复制代码
#define  _CRT_SECURE_NO_WARNINGS	1
#include <stdio.h>
#include <stdlib.h>
int main()
{
	int* p = (int*)malloc(4*sizeof(int));
	return 0;
}

结果运行图:

从结果运行图就可以看出来,开辟了一块连续的空间。我们就可以往这个空间里面存入一些数据了。前面,咱们讲过了,动态内存函数开辟的是一块连续的空间和数组开辟的方式一样 。所以,我们往里面存值和取值的方式和数组是一样的。进行代码展示:

javascript 复制代码
#define  _CRT_SECURE_NO_WARNINGS	1
#include <stdio.h>
#include <stdlib.h>
int main()
{
	int* p = (int*)malloc(4*sizeof(int));
	int i = 0;
	for (i = 0; i < 4; i++)
	{	
		*(p + i) = i + 1;  //存入数据1~4
	}
	for (i = 0; i < 4; i++)
	{
		printf("%d ", *(p + i));
	}
	return 0;
}

结果运行图:
我们还可以用数组的操作符' [ ]'进行访问,进行代码展示:

javascript 复制代码
#define  _CRT_SECURE_NO_WARNINGS	1
#include <stdio.h>
#include <stdlib.h>
int main()
{
	int* p = (int*)malloc(4 * sizeof(int));
	int i = 0;
	for (i = 0; i < 4; i++)
	{
		p[i] = i + 1;  //存入数据1~4
	}
	for (i = 0; i < 4; i++)
	{
		printf("%d ",p[i]);
	}
	return 0;
}

结果运行图:
对于动态内存函数开辟的空间的使用,我们完全可以类比数组的使用方式

  • malloc函数使用注意事项

    • 1.如果开辟空间成功,就会返回这个空间的起始地址。
    • 2.如果开辟空间失败,就会返回NULL,所以一定要做这个函数返回值的检查。
    • 3.如果这个函数的参数为0,这个函数具体怎么做是标准未定义的,取决于编译器,在VS中,就什么也不做。
  • free函数的介绍free函数具有释放内存的作用。当我们创建的空间不再使用时,就要释放内存空间。这就好比,咱们从图书馆里面借书。如果你只借不换的话,图书馆里面的书总有一天就空了,而且你一直借着,别人也借不了。内存也是这个道理。

  • free函数的使用:我们先来看这个函数的结构:

    void free (void* ptr);
    1.这个函数无返回值。
    2.这个函数的参数是个指针类型,我们需要把我们开辟的空间起始地址传给free函数,才能释放空间

进行代码展示:

javascript 复制代码
#define  _CRT_SECURE_NO_WARNINGS	1
#include <stdio.h>
#include <stdlib.h>
int main()
{
	int* p = (int*)malloc(4 * sizeof(int));
	free(p);
	p = NULL;
	return 0;
}

结果运行图:创建的空间。

释放后空间:

  • 使用注意事项:
    • 1.free函数不可以释放栈区和静态区的变量空间,只能释放堆区(动态内存)的空间。进行代码展示:
javascript 复制代码
#define  _CRT_SECURE_NO_WARNINGS	1
#include <stdio.h>
#include <stdlib.h>
int main()
{
	int i = 0;
	free(&i);
	return 0;
}

结果运行图:

  • 2.free释放空间后,指针变量要立马赋予NULL,防止成为野指针(关于野指针的成因咱们讲过了)。当我们释放完空间后,虽然我们的指针变量里面还存放着那块内存的地址(未赋予NULL前)。但是,我们已经没有访问那块空间的权限了,free之后就把这个关系给嘎断了 。如图所示:
    所以,我们一般都会申请内存函数和free函数一起使用。

calloc函数和realloc函数

  • calloc函数的介绍这个函数也是申请内存的函数 ,和malloc类似 。但是,这个函数可以指定申请的空间数目和申请的空间大小。进行结构展示:

    void* calloc (size_t num, size_t size);
    这个函数表示你要申请多少num,空间大小为size的空间。

  • calloc函数的使用:进行代码展示:

javascript 复制代码
#define  _CRT_SECURE_NO_WARNINGS	1
#include <stdio.h>
#include <stdlib.h>
int main()
{
	int i = 0;
	int* p = (int*)calloc(5,4); //申请5个空间大小为4个字节的空间
	if (p ==NULL)
		return 1;
	for (i = 0; i < 5; i++)
	{
		p[i] = i + 1;
	}
	for (i = 0; i < 5; i++)
	{
		printf("%d ", p[i]);
	}
	free(p);
	p = NULL;
	return 0;
}

结果运行图:

  • malloc函数和calloc函数的区别这俩的功能是一样的 。但是,它们俩的唯一(主要)区别就是:malloc函数开辟好空间后,不会初始化。calloc函数开辟好空间后,会初始化 。进行结果图的展示--这里只展示calloc的图,malloc的图上面展示过了:

  • realloc函数的介绍这个函数的功能就是在原来开辟的空间上进行空间大小的更改。比如,当我们觉得空间小了,我们就可以在原来空间的基础上扩大空间。或者我们开辟的空间太大了,我们就可以在原来的基础上减少空间。进行结构展示:

    void* realloc (void* ptr , size_t size) ;
    1.ptr是原来空间的起始地址,因为我们是在原来的空间基础上更改空间的,所以要知道原来空间的地址
    2.size表示我们要更改后的空间大小。

  • realloc函数的使用:进行代码展示:

javascript 复制代码
#define  _CRT_SECURE_NO_WARNINGS	1
#include <stdio.h>
#include <stdlib.h>
int main()
{
	int* p = (int*)calloc(5, 4); //申请5个空间大小为4个字节的空间
	int* pp = (int*)realloc(p, 3*sizeof(int));   //把空间缩小到3个int型的空间大小
	if (pp == NULL)
		return 1;
	else
		p = pp;
	free(p);
	p = NULL;
	return 0;
}

结果运行图:

  • ```realloc```的返回值注意(限于扩大空间) :关于它的返回值要有两点的注意:
    • 1.当我们在原来空间的基础上开辟空间时,如果后面的空间足够时,就返回原来的空间地址。
    • 2.当后面的空间不足时,realloc就会重新再找一块空间,进行新的空间开辟,然后返回新的空间地址。它先把原来的数据拷贝到新的空间里面,然后把原来的空间给释掉。如图所示:
    • 3 .因为realloc函数有可能返回新的地址,但是空间开辟不足时就会返回NULL。如果,我们直接用同一个指针来接收返回值时,如果返回的是NULL,那么指针内容就会被更改,原来的空间也无法访问了。返回的不是NULL还好,就怕万一返回的是NULL。为了出现这种情况,我们要做个判断和中间变量。进行代码展示:
javascript 复制代码
    int* p = (int*)calloc(5, 4); //申请5个空间大小为4个字节的空间
	int* pp = (int*)realloc(p, 3*sizeof(int));   //把空间缩小到3个int型的空间大小
	if (pp == NULL)
		return 1;
	else
		p = pp;

常见的动态内存的错误

  • 1.对NULL指针的解引用操作,进行代码展示:
javascript 复制代码
void test()
 {
        int *p = (int *)malloc(INT_MAX/4);
        *p = 20;      //如果p的值是NULL,就会有问题
        free(p);
 }

当我们开辟的空间过大时,就可能会返回NULL,一旦p被赋予NULL,我们就无法访问。在之间加个判断部分就OK了。

  • 2 .对动态开辟空间的越界访问,进行代码展示:
javascript 复制代码
void test()
 {
    int i = 0;
    int *p = (int *)malloc(10*sizeof(int));
    if(NULL == p)
    {
       exit(EXIT_FAILURE);
    }
    for(i=0; i<=10; i++)
    {
       *(p+i) = i;//   当i是10的时候越界访问
    }
       free(p);
 }

当我们访问的空间超过了我们原本的空间大小,就会发生错误。

  • 3.对非动态开辟内存使用free释放,进行代码展示:
javascript 复制代码
void test()
{
     int a = 10;
     int *p = &a;
    free(p);          //ok?
}

咱们讲过了,free只能释放动态内存函数开辟的空间,其它情况都不能释放。

  • 4.使用free释放一块动态开辟内存的一部分,进行代码展示:
javascript 复制代码
void test()
{
     int *p = (int *)malloc(100);
      p++;
     free(p);      //p不再指向动态内存的起始位置
}

我们开辟多少的空间,就要释放多少的空间,所以要给起始地址。否则就报错。

  • 5.对同一块动态内存多次释放,进行代码展示:
javascript 复制代码
void test()
{
    int *p = (int *)malloc(100);
     free(p);
     free(p);       //重复释放
}

这个很好理解,我们已经释放过内存了,干嘛还要继续释放同一块空间呢,这不是纯做没用功吗。

  • 6.动态开辟内存忘记释放(内存泄漏),进行代码展示:
javascript 复制代码
void test()
{
      int *p = (int *)malloc(100);
     if(NULL != p)
     {
         *p = 20;
     }
}
int main()
{
    test();
    while(1);
}

我们在使用完动态内存函数开辟的空间后,记住释放空间,也就是说动态内存函数和free是配套用的。

  • 补充
    • 1.咱们说过,用完内存后就要释放。其实有时候,当我们忘记释放内存的时候,程序运行结束后,会自动释放内存的。但是,咱们还是要养成随用随释放的好习惯。
    • 2.动态内存函数成功开辟空间后,会返回这个空间的起始地址,否则就返回NULL,所以要判断(检查)返回值。

彩蛋时刻!!!

https://www.bilibili.com/video/BV15K41147o7/?spm_id_from=333.788.recommend_more_video.0&vd_source=7d0d6d43e38f977d947fffdf92c1dfad
每章一句:我要对自己有耐心,因为我知道这是成长需要的。感谢你能看到这里,点赞+关注+收藏+转发是对我最大的鼓励,咱们下期见!!!

相关推荐
肘击鸣的百k路4 分钟前
Java 代理模式详解
java·开发语言·代理模式
捕鲸叉13 分钟前
MVC(Model-View-Controller)模式概述
开发语言·c++·设计模式
wrx繁星点点29 分钟前
享元模式:高效管理共享对象的设计模式
java·开发语言·spring·设计模式·maven·intellij-idea·享元模式
真的想不出名儿33 分钟前
Java基础——反射
java·开发语言
努力编程的阿伟1 小时前
【Java SE语法】抽象类(abstract class)和接口(interface)有什么异同?
java·开发语言
包饭厅咸鱼1 小时前
QML----复制指定下标的ListModel数据
开发语言·数据库
bryant_meng1 小时前
【python】Distribution
开发语言·python·分布函数·常用分布
红黑色的圣西罗1 小时前
Lua 怎么解决闭包内存泄漏问题
开发语言·lua
yanlou2331 小时前
KMP算法,next数组详解(c++)
开发语言·c++·kmp算法
小林熬夜学编程1 小时前
【Linux系统编程】第四十一弹---线程深度解析:从地址空间到多线程实践
linux·c语言·开发语言·c++·算法