Bootstrap

EP15:动态内存管理概述(c语言)malloc,calloc,realloc函数的介绍使用及柔性数组的介绍

目录

序言:在c语言中,什么是动态内存

1.malloc函数简述

1.1free函数

1.2 malloc函数

2.calloc函数简述

3.对malloc函数与calloc函数进行总结

3.1 不同点 (函数参数上)

3.2 相同点(函数使用步骤上)

4.重要的realloc函数的简介与使用

4.1 对realloc函数的使用进行简述

4.2 以calloc函数进行开辟为例,扩容的公式

4.2.1calloc函数扩展内存空间的方式

5.柔性数组 

5.1 柔性数组概述

5.2 柔性数组的使用

ps:"->"操作符和"."(点)操作符的使用


如果学习方向是c++方向那么c语言有三个板块的知识是非常重要的. 1:指针 2:结构体 3;动态内存管理.

序言:在c语言中,什么是动态内存

C语言中的动态内存是指在程序运行时,根据需要动态地分配内存空间的一种内存管理方式。与静态内存相比,动态内存的大小和生命周期都可以在程序运行时动态地确定和调整,因此更加灵活。C语言中提供了四个函数:malloc、calloc、realloc和free,用于动态地分配和释放内存空间。其中,malloc和calloc用于分配内存空间,realloc用于调整已分配内存空间的大小,free用于释放已分配的内存空间。动态内存的使用需要引用头文件<stdio.h>或<malloc.h>。

在本篇文章中,我们将着重简述何为malloc函数,calloc函数,free,以及柔性数组

1.malloc函数简述

1.1free函数

C语言提供了另外⼀个函数free,专门是用来做动态内存的释放和回收的,函数原型如下:

 void free (void* ptr);

free函数用来释放动态开辟的内存。

 但凡涉及到动态内存的开辟,释放空间这一步作为最后一步绝对是不可或缺的.

1.2 malloc函数

函数头文件: #include <stdlib.h>

函数参数: void* malloc (size_t size);

函数作用: 用于开辟动态内存

从代码示例看函数的具体使用方法

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main()
{
    int* p = (int*)malloc(10 * sizeof(int));//1.动态内存的开辟(指针p所指向的是malloc函数开辟空间的起始地址)

     malloc():括号里面要写的是开辟多少大小的内存空间,一般用:任意整数*sizeof(整型)
    if (p == NULL)
    {
        perror("malloc");
        return 1;
    }


    int i = 0;
    for (i = 0; i < 10; i++)//2.对动态内存开辟的空间进行访问

    {
        *(p + i) = i;//使用指针p去访问malloc开辟的空间里面的元素,再解引用赋值给i
    }


    for (i = 0; i < 10; i++)//3.对开辟的空间中的内容进行打印
    {
        printf("%d ",i );//将刚才访问的空间里面存储的数据打印出来
    }


    free(p);//4.对开辟的空间进行回收
    p=NULL;
    return 0;
 }

为啥要这么写:  int* p = (int*)malloc(10 * sizeof(int));

解释:由函数参数 void* malloc (size_t size);可知malloc函数一个空指针类型的函数,但是由于空指针是不可以直接用于指针的运算的,所以我们要将它强制类型转换成我们想要的类型.

一点联想:这一点让我想起了qsort函数,感觉void*类型的指针都要进行这样的操作,可以在这里留意一下并且进

行相关的知识迁移以便日后遇到相似知识点的学习

2.calloc函数简述

总而言之,calloc函数的使用方法以及功能和malloc函数基本一致

函数头文件: #include <stdlib.h>

函数参数: void* calloc (size_t num, size_t size);

函数作用: 用于开辟动态内存

从代码示例看函数的具体使用方法

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main()
{
    int* p = calloc(10, sizeof(int));//1.动态内存的开辟
    if (p == NULL)
    {
        perror("calloc");
        return 1;
    }


    int i = 0;//2.对动态内存开辟的空间进行访问
    for (i = 0; i < 10; i++)
    {
        *(p + i) = i;
    }


    for (i = 0; i < 10; i++)//3.对开辟的空间中的内容进行打印
    {
        printf("%d ", *(p + i) );
    }


    free(p);//4.对开辟的空间进行回收
    p = NULL;

    return 0;
}

3.对malloc函数与calloc函数进行总结

3.1 不同点 (函数参数上)

以上述两段代码为例

malloc函数是:int* p = (int*)malloc(10 * sizeof(int));

calloc函数是: int* p = (int*)calloc(10, sizeof(int));

观察上述两段代码,其实也没啥不同点,就是把函数括号里面我们想要开辟的空间的那个表达式把逗号改成了*号的区别嘛

3.2 相同点(函数使用步骤上)

首先要明确的是它们都要进行强制类型转换转换成我们想要的指针类型.

最重要的是,它们是使用步骤可以分成四步

1.动态内存的开辟

2.对动态内存开辟的空间进行访问

3.对开辟的空间中的内容进行打印

4.对开辟的空间进行释放

简化一下就是: 1.开辟 2.访问 3.使用 4.释放

这两个函数在这四个步骤上的写法是一摸一样,没有任何区别的.将此四部先后逻辑顺序理清并进行适当的记忆这两个动态内存函数便可以说是掌握了.

所以此二者等价

4.重要的realloc函数的简介与使用

4.1 对realloc函数的使用进行简述

 在本文的开头阐述过何为动态内存"...动态内存的大小和生命周期都可以在程序运行时动态地确定和调整,因此更加灵活。..."

故而没有realloc函数的介入很难把一段可正常运行的代码叫做"动态内存管理"

函数头文件: #include <stdlib.h>

函数参数: void* realloc (void* ptr, size_t size);

对函数参数的解释: void*ptr就是要进行扩展的对象,size_t size就是要将被扩展的扩展至管理员预期的空间

函数作用: 用于动态内存的扩展,要和malloc函数或者calloc函数进行联合使用

具体且形象的讲解realoc函数的作用:

realloc的作用就是将原本malloc和calloc开辟的空间扩大到多少
举个例子就是说如果malloc或calloc是一段单向路上不与起点重合的一个质点,calloc就是另外一个质点
用calloc质点到起点的距离减去malloc,calloc质点所在的距离便是calloc函数所追加的空间

如图


 

从代码示例看函数的具体使用方法

就是在malloc函数和calloc函数原先的使用步骤上加上扩容这一步

1.动态内存的开辟

2.对动态内存开辟的空间进行访问

3,对malloc函数或calloc函数开辟的空间进行扩容

4.对开辟的空间中的内容进行打印

5.对开辟的空间进行释放

总结下来就是 1.开辟 2.访问 3,扩容 4.使用 5.释放

4.2 以calloc函数进行开辟为例,扩容的公式

    int* ptr = (int*)realloc(p, 15 * sizeof(int));//扩展开辟空间
    if (p != NULL)
    {
        p = ptr;
    }
    else
    {
        perror("errno");
        return 1;
    } 

为什么这么写: int* ptr = (int*)realloc(p, 15 * sizeof(int));

这里便涉及到calloc函数扩展内存空间的方式了

4.2.1calloc函数扩展内存空间的方式

情况1:扩展失败,返回NULL,于是便有了 

 else
    {
        perror("errno");
        return 1;
    } 

情况2;扩展成功了,于是便有了

if (p != NULL)
    {
        p = ptr;
    }

扩展成功方式1:

在malloc函数或者calloc函数开辟好的空间后进行扩容,如果没有足够的空间进行扩大时候,此时的calloc函数会在堆区中重新选择一块大小满足需求的空间,同时将旧空间中的旧数据连同着一块拷过来,这也是为啥虽然上述代码  int* ptr = (int*)realloc(p, 15 * sizeof(int));虽然写的是15 * sizeof(int)实际上只是扩展了5*sizeof(int)大小的空间.然后释放就空间,同时返回新的空间.

扩展成功方式1:

若空间大小足够,则在已经开辟好的空间直接进行追加空间进行扩展,扩大空间后,直接返回就空间的起始地址.

4.3 从示例看realloc函数的具体用法 

根据上面总结的五个步骤来写:  1.开辟 2.访问 3,扩容 4.使用 5.释放

int main()// 开辟 访问   扩容 使用 释放
{
    //开辟
    int* p = (int*)calloc(10, sizeof(int));
    if (p == NULL)
    {
        perror("calloc");
        return 1;
    }

    //访问
    int i = 0;
    for (i = 0; i < 10; i++)
    {
        *(p + i) = i;
    }

    //扩容
     int* ptr = (int*)realloc(p, 15 * sizeof(int));//扩展开辟空间
    if (p != NULL)
    {
        p = ptr;
    }
    else
    {
        perror("errno");
        return 1;
    }

    //使用
    for (i = 0; i < 10; i++)
    {
        printf("%d ", *(p + i) );
    }

    

    //释放
    free(p);
    p = NULL;

    return 0;
}

ps:其实realloc函数除了调整空间外,也可以实现和malloc或者realloc函数一样的功能

int*p=(int*)realloc(NULL,1o*sizeof(int));等价于malloc或者calloc函数,只是一般不这么去写.

5.柔性数组 

5.1 柔性数组概述

 C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员。

结构体成员的特点

• 结构中的柔性数组成员前面必须至少一个其他成员。

• sizeof 返回的这种结构大小不包括柔性数组的内存。

• 包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。

5.2 柔性数组的使用

以malloc函数为例,柔性数组的使用依旧遵循上述的五个步骤,不同的是要先创建一个结构体

1. 开辟 2.访问 3.扩容 4.使用 5.释放

struct st
{
    char c;
    int n;
    int arr[0];
};


int main()
{
    struct st* ps = (struct st*)malloc(sizeof(struct st) + 10 * sizeof(int));//1.开辟
    //前面的sizeof(struct st)是计算此结构体本来的大小
    //后面的 10 * sizeof(int) 意思是将柔性数组成员开辟成多少大小的

    if (ps == NULL)
    {
        perror("malloc");
        return 1;
    }

    ps->c = 'w';//2.访问
    ps->n = 100;
    int i = 0;
    for (i = 0; i < 10; i++)
    {
        ps->arr[i]=i;//也可以这么写 QUESTION2;点操作符和箭头操作符的区别,在那些情况下一般用什么的总结归纳
    }

    struct st* ptr = (struct st*)realloc(ps, sizeof(struct st) + 15 * sizeof(int));//3.扩容
    if (ptr != NULL) 
    {
        ps = ptr;
    }
    else
    {
        perror("realloc");
        return 1;
    }

    for (i = 0; i < 10; i++)//4.使用
    {
        printf("%d ", i);
    }
    printf("\n");
        printf("%c \n", ps->c);
        printf("%d \n", ps->n);


    free(ps);//5.释放
    ps = NULL;
    return 0;
}

ps:"->"操作符和"."(点)操作符的使用

1.点操作符的使用情况

直接使用

  struct st
{
    int a;
    int b;
};
int main1()
{
    struct st s = { .a = 10 , .b=20 };
    printf("%d %d ", s.a, s.b);
    return 0;
}

2.->的使用情况

由柔性数组的实例情况使用可知,在"结构体+指针"的情况下如果要使用指针对结构体中某一个成员变量进行访问,那么就是"指针->结构体成员变量名=程序员想要赋的值".

封面如下

 

;