详解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语言动态内存管理的资料请关注脚本之家其它相关文章!

相关文章

  • Qt学习教程之对话框消失动画效果

    Qt学习教程之对话框消失动画效果

    这篇文章主要给大家介绍了关于Qt学习教程之对话框消失动画效果的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2018-07-07
  • C语言超全面define预处理指令的使用说明

    C语言超全面define预处理指令的使用说明

    C语言里可以用#define定义一个标识符来表示一个常量。特点是:定义的标识符不占内存,只是一个临时的符号,预编译后这个符号就不存在了,也不做类型定义。预编译又叫预处理
    2022-04-04
  • 最新clion2020激活码附安装教程(亲测有效)

    最新clion2020激活码附安装教程(亲测有效)

    这篇文章主要介绍了最新clion2020激活码附安装教程(亲测有效),本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-11-11
  • C++ Qt开发之使用QProcess实现进程管理

    C++ Qt开发之使用QProcess实现进程管理

    Qt 是一个跨平台C++图形界面开发库,利用Qt可以快速开发跨平台窗体应用程序,本文将重点介绍如何运用QProcess组件实现针对进程的控制管理等,感兴趣的可以了解下
    2024-03-03
  • 详解C++中的内存同步模式(memory order)

    详解C++中的内存同步模式(memory order)

    这篇文章主要介绍了C++中的内存同步模式,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-04-04
  • c语言中malloc、realloc与calloc 的区别以及联系

    c语言中malloc、realloc与calloc 的区别以及联系

    以下是对c语言中的malloc函数,realloc函数与calloc函数的区别以及它们之间的联系进行了介绍,需要的朋友可以过来参考下
    2013-08-08
  • 二进制、八进制 、十进制、十六进制之间转换的原理详解

    二进制、八进制 、十进制、十六进制之间转换的原理详解

    本文介绍了进制的概念及其在C语言编程中的应用,进制是进位制的简称,描述了数值在不同进制下的表示方法,常见的进制包括二进制、八进制和十六进制,二进制使用0和1表示,八进制使用0-7数字表示,十六进制使用0-9和A-F表示,文章还介绍了如何在不同进制之间进行转换
    2024-11-11
  • C++ 11 nullptr 空指针示例详解

    C++ 11 nullptr 空指针示例详解

    C++11标准引入了nullptr来替代传统的NULL,解决了NULL可能导致的类型混淆问题,nullptr是nullptr_t类型的实例,专用于初始化空类型指针,与整型不会发生隐式转换,从而使代码更健壮,它可以被隐式转换为任意类型的指针,提高了代码的安全性和可读性
    2024-10-10
  • 如何使用arm-none-eabi-gcc编译器搭建STM32的Vscode开发环境

    如何使用arm-none-eabi-gcc编译器搭建STM32的Vscode开发环境

    这篇文章主要介绍了使用arm-none-eabi-gcc编译器搭建STM32的Vscode开发环境,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-07-07
  • C++右值引用与移动构造函数基础与应用详解

    C++右值引用与移动构造函数基础与应用详解

    左值和右值都是针对表达式,左值是指表达式结束后依然存在的持久对象,右值是指表达式结束时就不再存在的临时对象,下面这篇文章主要给大家介绍了关于C++11右值引用和移动语义的相关资料,需要的朋友可以参考下
    2023-02-02

最新评论