详解C语言中的动态内存管理

 更新时间:2022年12月12日 10:02:57   作者:小马学习代码  
对于数据的存储我们可以静态存储,也可以动态存储,两种方式都有自己特有的好处,这篇文章教我们如和进行动态的数据存储!!!!感兴趣的小伙伴可以跟随小编一起学习一下

一、动态内存管理

1.1为什么要有动态内存管理

1.1.1  在c语言中我们普通的内存开辟是直接在栈上进行开辟的 

int i = 20;//在栈空间上开辟四个字节
int arr[10]={0}; //在栈中连续开辟四十个字节

这样开辟的特点是:

(1)他所开辟的空间是固定的 

(2)数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配

但对于空间的需求,我们有的时候并不知道,有可能空间开大了造成了浪费,也有可能空间开小了造成栈溢出,这样我们就需要一个动态的内存管理让我们需要多少内存的时候开辟多少。

1.2动态内存介绍

1.2.1malloc 和 free

void*   malloc  (size_t size);

这个函数想内存中申请一个连续的空间(是在堆中申请),并返回指向这块空间的指针。 

如果开辟成功,则返回一个指向开辟好空间的指针。

如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。

返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己 来决定。

如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。

同样的C语言提供了另外一个函数free,专门是用来做动态内存的释放和回收的

void * free (void * ptr) 

free 是用来释放动态开辟的内存的 

如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。

如果参数 ptr 是NULL指针,则函数什么事都不做。

#include<stdio.h>
#include<stdlib.h>  //malloc 和free 都在stdlib.h的头文件里
int main()
{
    int arr[10] ={0}; //这是在栈中申请连续的四十个空间 是静态的
    int * arr1;
    int *ptr ;
    ptr =(int*)malloc (10*sizeof(int)); //申请一个动态内存空间为40字节
    if(ptr==NULL) //防止申请空间失败传入了空指针
    {
        perror("ptr");
    }
    arr1=ptr;
    free(arr1);  //结束后要进行一个空间的释放
    arr1=NULL;      //然后在指向空指针防止出现了野指针
    //这就是申请一个动态内存空间的套用过程
    
    return 0;
}

1.2.2 calloc

c语言同样的提供了一个函数calloc,也是用来动态内存的分配

void* calloc (size_t num, size_t size);

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

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

#include<stdio.h>
#include<stdlib.h>  //malloc 和free 都在stdlib.h的头文件里
int main()
{
    int arr[10] ={0}; //这是在栈中申请连续的四十个空间 是静态的
    int * arr1;
    int *ptr ;
    ptr =(int*)calloc (10,sizeof(int)); //申请一个动态内存空间为40字节
    if(ptr==NULL) //防止申请空间失败传入了空指针
    {
        perror("ptr");
    }
    arr1=ptr;
    free(arr1);  //结束后要进行一个空间的释放
    arr1=NULL;      //然后在指向空指针防止出现了野指针
    //这就是申请一个动态内存空间的套用过程
    
    return 0;
}

1.2.3 realloc

realloc 使我们申请的的动态内存空间变得灵活,在申请动态内存空间的时候,有时候我们申请的过大,或者申请的过小的时候,我们可以通过realloc也对我们申请的空间进行一个合理的调整改变

void* realloc (void* ptr, size_t size);

  • ptr 是要调整的内存地址
  • size 调整之后新大小
  • 返回值为调整之后的内存起始位置

这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到 新 的空间。

这有两种调节:

第一种是在你原来的内存上进行了一个改变(内存改变不大),就是在原有的内存空间进行加大空间。

第二种就是原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另找一个合适大小的连续空间来使用。这样函数返回的是一个新的内存地址。

#include<stdio.h>
#include<stdlib.h>  //malloc 和free 都在stdlib.h的头文件里
int main()
{
    int arr[10] ={0}; //这是在栈中申请连续的四十个空间 是静态的
    int * arr1;
    int *ptr ;
    ptr =(int*)calloc (10,sizeof(int)); //申请一个动态内存空间为40字节
    if(ptr==NULL) //防止申请空间失败传入了空指针
    {
        perror("ptr");
    }
    arr1=ptr;
    arr1 =(int*)realloc (arr1,10000);  //改变原有的内存空间
    free(arr1);
    arr1=NULL;
    ptr=NULL; 
    return 0;
}

1.3常见的动态内存错误

1.3.1对NULL指针解引用操作

#include<stdio.h>
#include<stdlib.h>
int main()
{
    int *ptr;
    ptr=(int*)malloc(sizeof(int));
    *ptr=1;  //这里有可能申请失败 ,但我这没有失败,为了以防万一还是需要进行判断一下,正确的申请在上面
    free(ptr);
    ptr=NULL;
    
    
    return 0;
}

1.3.2对动态内存的越界

#include<stdio.h>
#include<stdlib.h>  //malloc 和free 都在stdlib.h的头文件里
int main()
{
    int *ptr ;
    ptr =(int*)malloc(40); //申请一个动态内存空间为40字节
    if(ptr==NULL) //防止申请空间失败传入了空指针
    {
        perror("ptr");
    }
    
    for(int i=0;i<=11;i++)
    {
        *(ptr+i)=i;   //申请的是四十个字节,这里产生了越界
    }
    for(int i=0;i<=11;i++)
    {
        printf("%d ",*(ptr+i));
    }
    free(ptr);
    ptr=NULL;
    return 0;
}

1.3.3对非动态空间进行释放

#include<stdio.h>
#include<stdlib.h>
int main()
{
    int *p;
    *p=10;  
    free(p);  //这里的p并不是动态内存空间仍然进行了释放
    return 0;
}

1.3.4 动态内存空间的部分释放

#include<stdio.h>
#include<stdlib.h>
int main()
{
    int *p=(int*)malloc(sizeof(int)*2);
    if(p==NULL)
    {
        perror("p");
    }
    p++;
    free(p); //这里的p的地址并不是起始地址,只是进行了部分的释放
    p=NULL;
   
}

1.3.5对一块动态内存进行多次释放

#include<stdio.h>
#include<stdlib.h>
int main()
{
    int *p=(int*)malloc(sizeof(int)*2);
    if(p==NULL)
    {
        perror("p");
    }
    
    free(p);
   // ~~~~~~~~~~
    free(p);  //  已经释放p了有进行了释放
    p=NULL;
   
}

这个真的有可能发生,当我们代码写的比较长的时候,我们有可能忘了我们是否已经释放这块空间,就有可能进行重复的释放,这是不正确的,而解决他的方法是,当我们释放了一块空间后,一定让他指为空指针。

1.3.6动态内存忘记释放(内存泄漏)

#include<stdio.h>
#include<stdlib.h>
void test(int *p)
{
     p=(int*)malloc(sizeof(int)*2);
    if(p==NULL)
    {
        perror("p");
    }
}
int main()
{
    int *ptr;
    test(ptr); //这里就是没有对内存进行释放
   
}

总结:

对于动态内存还是比较重要的,因为堆的空间是比栈的空间的是大的,同时我们要知道,动态的是可以进行修改的,我们需要多少内存就可以开辟多少内存,防止了内存的浪费,但是我们在申请动态内存的时候一定要防止一些不必要的错误不然就会得不偿失。

以上就是详解C语言中的动态内存管理的详细内容,更多关于C语言动态内存管理的资料请关注脚本之家其它相关文章!

相关文章

  • C++定时器Timer在项目中的使用方法

    C++定时器Timer在项目中的使用方法

    这篇文章主要给大家介绍了关于C++定时器Timer在项目中的基本使用方法,文中通过示例代码介绍的非常详细,对大家学习或者使用C++具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-05-05
  • C语言 柔性数组的使用详解

    C语言 柔性数组的使用详解

    柔性数组(Flexible Array)是引入的一个新特性,它允许你在定义结构体时创建一个空数组,而这个数组的大小可以在程序运行的过程中根据你的需求进行更改特别注意的一点是:这个空数组必须声明为结构体的最后一个成员,并且还要求这样的结构体至少包含一个其他类型的成员
    2022-03-03
  • VScode+ESP32简单环境搭建

    VScode+ESP32简单环境搭建

    本文章向大家介绍ESP32-C3搭建环境教程,主要包括ESP32-C3搭建环境教程使用实例,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-06-06
  • 深入解析C语言中typedef的四个用途

    深入解析C语言中typedef的四个用途

    以下是对C语言中typedef的四个用途进行了详细的分析介绍,需要的朋友可以过来参考下
    2013-08-08
  • C++中的6种构造函数举例详解

    C++中的6种构造函数举例详解

    这篇文章主要介绍了C++中的6种构造函数的相关资料,C++中构造函数用于类对象初始化,类型包括默认构造函数、参数化构造函数、拷贝构造函数等,默认构造函数通常不需要参数,编译器会自动生成,除非存在其他构造函数,需要的朋友可以参考下
    2024-10-10
  • Qt实现字幕滚动效果的示例代码

    Qt实现字幕滚动效果的示例代码

    这篇文章主要介绍了Qt如何利用QTimer实现字幕滚动功能,并且可以实现自行更改文本内容、自适应文本大小、自由调整速度等功能,感兴趣的可以学习一下
    2022-06-06
  • 详解C语言数据结构之栈

    详解C语言数据结构之栈

    这篇文章主要为大家介绍了C语言数据结构之栈,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2022-01-01
  • C++设计模式编程中Facade外观模式的使用实例解析

    C++设计模式编程中Facade外观模式的使用实例解析

    这篇文章主要介绍了C++设计模式编程中Facade外观模式的使用实例解析,外观模式的主要用途就是为子系统的复杂处理过程提供方便的调用方法,需要的朋友可以参考下
    2016-03-03
  • 数据结构之AVL树详解

    数据结构之AVL树详解

    这篇文章主要介绍了数据结构之AVL树详解,本文非常细致的讲解了AVL树的基础知识、AVL树的旋转操作、AVL数的插入和删除操作等,需要的朋友可以参考下
    2014-08-08
  • C语言中char*和char[]用法区别分析

    C语言中char*和char[]用法区别分析

    这篇文章主要介绍了C语言中char*和char[]用法区别,包括使用过程中的误区及注意点分析,需要的朋友可以参考下
    2014-09-09

最新评论