5600字带你深入了解动态内存管理

简介: 5600字带你深入了解动态内存管理

@TOC


一、为什么会存在动态内存?

int data=20;//在栈空间上开辟4个字节空间
char ch[5]={0};//在栈开辟5个字节连续空间
🌞🌞🌞上面展示的即为我们正常开辟固定的内存空间,它有两个方面的特点
1.内存空间所占大小是固定的,不能改变的。
2.创建数组时,必须指明长度大小,在编译时内存进行分配。

在这里插入图片描述
很显然静态分配内存分配在一些场景,就暴露出它的弊端。如果在开发之前,我们不知道空间的需求,我们有时只有在程序运行的时候才能知道自己所需要空间大小,这时候我们只能使用动态分配内存了。

二、动态内存函数

1.malloc和free

在这里插入图片描述

🚩🚩🚩 malloc函数的参数只有一个size_t size,向内存申请一块连续可用的空间,有几点需要注意
==1.如果开辟成功的话,返回指向开辟好空间的指针==
==2.如果开辟失败的话,则返回NULL,因此每次开辟空间之后,都要进行检查==
==3.malloc函数未定义返回类型,一切由使用者自己使用==
==4.需引用stdlib.h头文件==

在这里插入图片描述

⚠️⚠️⚠️ free函数是和malloc配套使用的,每次在堆开辟动态空间后,程序结束之前,必须进行空间释放,不然会出现动态空间泄露,在使用free时,仍需要注意几点
==1.如果指针指向的空间不是动态开辟的,不能用free进行释放==
==2.如果指针指向的是null指针,则free函数什么事都不做==
==3.free不能多次使用==
==4.需引用stdlib.h头文件==

代码如下(示例):

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
int main()
{
    int* src = NULL;
    src = (int*)malloc(40);//开辟40字节动态内存
    if (src == NULL)
    {
        printf("%s", strerror(errno));
        return 1;
    }
    free(src);//进行动态内存释放
    src = NULL;
    return 0;
}
相信有人会问,不是已经对动态内存进行释放,为什么还要令指针等于NULL,我们调试一把。

在这里插入图片描述
在这里插入图片描述
⚠️⚠️⚠️这里我们可以发现,虽然动态内存进行free释放,但指针仍然指向被释放的动态内存的地址,如果不置空,就会造成野指针,非法访问的问题。

2.calloc

在这里插入图片描述

🚩🚩🚩 calloc和malloc最大的区别就是,malloc只负责对内存进行动态开辟,但calloc不仅开辟,还进行初始化。

代码如下(示例):

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
int main()
{
    int* src = (int*)calloc(10, sizeof(int));
    if (src == NULL)
        {
          printf("%s", strerror(errno));
          return 1;
        }
    free(src);//进行动态内存释放
    src = NULL;
    return 0;
}

在这里插入图片描述

我们调试一把可以发现,calloc在开辟空间时同时进行了初始化。所以如何我们对申请的内存空间的内容要求初始化,那么可以很方便的使用calloc函数来完成任务。

3.realloc

在这里插入图片描述

🚩🚩🚩 当我们一次开辟动态内存不够大的时候,realloc让动态内存更加的灵活。realloc几个参数:
==1.第一个参数为要调整内存的地址==
==2.调整后大小==
==3.调整后内存的起始位置==
为什么还要返回调整后内存的地址,不是直接就开辟好了吗?其实reallloc函数在开辟时有以下两种情况:
1.原来的内存之后空间是足够的,则直接开辟
2.原来的内存之后空间不够用。
==我们画图刨析一下==

在这里插入图片描述
在这里插入图片描述
情况1:直接追加空间,原来数据不变
情况2:没有足够的空间,在堆上找一个大小合适的连续空间。所以函数返回的是一个新的内存地址。

代码如下(示例)

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
int main()
{
    int* src = NULL;
    src = (int*)malloc(40);//开辟40字节动态内存
    if (src == NULL)
    {
        printf("%s", strerror(errno));
        return 1;
    }
    src = realloc(src, 80);
    if (src == NULL)
    {
        printf("%s", strerror(errno));
        return 1;
    }
    free(src);//进行动态内存释放
    src = NULL;
    return 0;
}

三、动态内存函数常见错误

1.动态内存越界访问

void test1()
{
    int* src = (int*)malloc(20);
    if (NULL == src)
    {
        return 1;
    }
    int i = 0;
    for (i = 0; i < 6; i++)
    {
        (*src+i)=i
    }
    free(src);
    src = NULL;
}

在这里插入图片描述

⚠️⚠️⚠️ 在这里我们malloc只开辟了20个字节,但(*src+5)造成了越界访问

2.对NULL指针进行解引用操作

void test2()
{
    int* src = (int*)malloc(INT_MAX);//此处INT_MAX为int的最大值
    *src = 10;//如果src是NULL时,无法解引用
    free(src);
    return 0;
}
⚠️⚠️⚠️ 这里未对开辟的动态内存空间进行是否为空的判断,当为空时,解引用就会出现错误。

3.使用free释放一块动态开辟内存的一部分

void test3()
{
    int* src = (int*)malloc(40);
    int i=0;
    for(i=0;i<6;i++)
    {
    *(src+i)=i;
    src++;
    }
    free(src);//此时src不指向起始位置
}

在这里插入图片描述

⚠️⚠️⚠️ 因为指针指向的地址发生变化,不在指向起始未知,进行free释放是非常危险的。

4.对静态内存进行free释放

void test4()
{
    int a = 20;
    int* src = &a;
    free(src);
}

5.对同一内存空间多次释放

void test5()
{
    int* src = (int*)malloc(40);
    free(src);
    free(src);//多次释放
}
⚠️⚠️⚠️ 第一个free已经将堆空间的动态内存进行释放,此时src已经是一个野指针,在进行释放是十分危险的。

6.动态开辟空间忘记释放

void test6()
{
    int* src = (int*)malloc(40);
    if (src != NULL)
    {

    }
    while (1);
}
⚠️⚠️⚠️ 在开辟动态内存之后,一直进行while循环,为进行free释放,会造成内存泄漏。

四、经典笔试题

1.笔试1:

void test(char* src)
{
    src = (char*)malloc(30);
}
int main()
{
    char* src = NULL;
    test(src);
    strcpy(src, "wo yao jin da chang");
    printf(src);
    free(src);
}
这里会输出wo yao jin da chang 吗?

在这里插入图片描述
在这里插入图片描述
⚠️⚠️⚠️这里很明显,src仍然是NULL,所以无法输出wo yao jin da chang

2.笔试2:

char* test()
{
    char arr[] = "wo yao jin da chang";
    return arr;
}
int main()
{
    char* src = NULL;
    src = tset();
    printf(src);
    return 0;
}
这里会输出wo yao jin da chang 吗?

在这里插入图片描述
⚠️⚠️⚠️这里test函数确实把字符串地址传给了src,但是字符串是局部变量,当函数执行完之后,就销毁了,所以src输出的内容是随机的。

3.笔试3:

void test()
{
    char* src = (char*)malloc(50);
    if (src != NULL)
    {
        strcpy(src, "wo yao jin da chang");
    }
    free(src);
    if (src != NULL)
    {
        strcpy(src, "taijuanlebujinle");
        printf(src);
    }
}
这里会输出taijuanlebujinle 吗?

在这里插入图片描述
⚠️⚠️⚠️这里对动态内存释放后,继续进行赋值,对野指针进行了访问是错误的。

总结

🚩🚩🚩 看到这里大家对动态内存管理已经有了一定的认识,应该特别注意这几点,在进行动态内存开辟之后进行判断是否为空,使用完后进行free释放,并且置空,防止动态内存泄露,只要记住这几点基本就可以很好的使用动态内存了。

在这里插入图片描述

目录
相关文章
|
存储 编译器 程序员
BIT-6自定义类型和动态内存管理(11000字详解)
BIT-6自定义类型和动态内存管理(11000字详解)
76 0
|
1月前
|
编译器 C语言
动态内存分配与管理详解(附加笔试题分析)(上)
动态内存分配与管理详解(附加笔试题分析)
49 1
|
1月前
|
程序员 编译器 C语言
动态内存分配与管理详解(附加笔试题分析)(下)
动态内存分配与管理详解(附加笔试题分析)(下)
46 2
|
5月前
|
存储 编译器 程序员
动态内存管理(想要了解动态内存管理,只需要看这一篇就足够了!)
动态内存管理(想要了解动态内存管理,只需要看这一篇就足够了!)
|
6月前
|
C语言
c语言小课设--通讯录(动态内存管理+可持久化数据)
c语言小课设--通讯录(动态内存管理+可持久化数据)
|
6月前
|
编译器 C语言
动态内存的管理(内存储存的god)
动态内存的管理(内存储存的god)
39 0
|
程序员 编译器 C语言
C语言进阶之通讯录的实现(静态版和动态版)以及动态内存管理(下)
数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配。
|
存储 C语言
C语言进阶之通讯录的实现(静态版和动态版)以及动态内存管理(上)
开头我们先断言,防止传NULL指针,第二步初始化长度为0,最后将结构体数组中的元素均初始化为0即可。
|
存储
通讯录的实现与在文件里面存储信息,多文件实现,开辟内存柔性数组
通讯录的实现与在文件里面存储信息,多文件实现,开辟内存柔性数组
52 0
|
程序员 编译器 C语言
C语言---认识动态内存管理并实现一个动态通讯录:静态通讯录别来沾边
C语言学习——动态内存管理(上)+优化版通讯录+笔试题