进阶C语言 第五章-------《动态内存管理》 (malloc、free,calloc、realloc、柔性数组、C/C++程序在内存)知识点+完整思维导图+基本练习题+深入细节+通俗易懂+建议收藏(一0

简介: 进阶C语言 第五章-------《动态内存管理》 (malloc、free,calloc、realloc、柔性数组、C/C++程序在内存)知识点+完整思维导图+基本练习题+深入细节+通俗易懂+建议收藏(一0

绪论

       书接上回,本章来到动态内存管理,这章的知识相较于结构体来说来简单一点,但是有许多地方需要注意不能马虎,并且该章的知识也比较重要,通过名称可以知道动态的内存管理,这样就可以对内存有一个很方便的管理方法!

image.png

所以安全带系好,发车啦(建议电脑观看)。



思维导图:

image.png


要XMind思维导图的话可以私信哈


目录


1.动态内存分配存在的意义

2.动态内存函数

2.1malloc

2.2free

2.3callloc

2.4realloc

3.动态内存常见的错误

3.1对NUL指针的解应用操作

3.2对动态内存开辟的空间越界访问

3.3对非动态开辟的内存进行free释放

3.4使用free释放动态内存开辟的一部分空间

3.5对同一块动态内存空间多次释放

3.6动态内存空间的忘记释放

4.动态内存常见问题、笔试题

5.C/C++程序在内存中的内存开辟

6.柔性数组

1.动态内存分配存在的意义

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

下面通过对比来解释:

在我们一般申请内存的时候都是通过创建变量类型来申请的空间

如:int(4byte)、char(1byte)、int [ ]、结构体、联合体、枚举......

而这些内存的空间当我们创建好后他就固定死了,就比较的局限(死板)。

现在C语言提供了一种方式就是动态内存管理(函数)当我们需要时就创建一定的空间,当空间不够后又可以再次的补充其空间,当然当空间过大也可以减小空间的大小

2.动态内存函数

2.1malloc

知识点:

image.png

对于malloc函数来说他的类型是void *malloc(size_t);

用法:申请一个连续可用的size字节大小的空间,开辟成功时返回指向这块空间的指针

头文件:#include<stdlib.h>

所以一般的用法是要将返回来的void * 强转成自己所需要的指针类型

如:

int main()
{
    //当你要开辟多个以整型大小为基础的空间时
    int * ptr = (int *)malloc(sizeof(int) * 10);//当然你也可以直接在()内写成“40”byte
    //对于上面因为你需要开辟的是整形个大小的空间所以最后将返回的指针强转成整形指针类型
    //并且ptr也要是整形指针这是你所要用的,你甚至可以把它想像成开辟了一个大小为10的整形数组
    return 0;
}

为什么这样写我已经写上了注释

细节:

既然是申请那就有可能会失败,所以当申请失败的时候系统会返回一个空指针(NULL)

所以在我们写完后要加上一个判断,判断其是否申请成功

    if (ptr == NULL)
    {
        printf("%s\n", strerror(errno));//打印错误
        //perror("ptr");//打印错误
        return 0;
    }

image.png


不能开辟0byte的空间

判断空间是否申请成功

用free释放申请好的空间(并且将其指针置为空)

练习        

用动态内存分配的空间来打印打印1~10:

分析:

因为最终传回来的是指针所以就可以通过指针的方法来进行内存的访问,并且所开辟的大小也是以整形大小一个个开辟的,所以直接+1即可

 #define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
int main()
{
    //当你要开辟多个以整型大小为基础的空间时
    int* ptr = (int*)malloc(40);
    if (ptr == NULL)
    {
        perror("ptr");//打印错误
        return 0;
    }
    for(int i = 0; i<10 ; i++)
    {
        *(ptr+i) = i +1;
       printf("%d ",*(ptr + i)); 
    }
    free(ptr);//释放向内存中借的空间
  ptr = NULL;
    return 0;
}


2.2free

知识点:

函数:void free (void* ptr);

当我们向操作系统借了的空间所以用完了后我们需要将其归还(若不归还的话对这空间再你没有结束程序的过程中都处于被借的状态也就浪费了这片没有用的空间)

用法:释放所传递过来的动态内存开辟的指针所指向的空间

细节:

对于free所释放的空间,虽然将其所存的内存归还给了操作系统,但ptr所指地址并不会被其改变,所以为了防止我们后面不小心还使用ptr这个地址指向的被已经释放的内存,所以需要再将ptr置为NULL

free(ptr);
ptr = NULL;

当所传递过来的是NULL时,free什么都不做。

不能释放不是动态内存开辟的空间

2.3callloc

知识点:

void* calloc (size_t num, size_t size);

该函数的意义和malloc一样,都是用来开辟空间的。

但是其参数不相同,num表示的是元素个数,而size表示的是每个元素的大小,返回值一样

所以对于之前用malloc开辟的10个整形大小的空间(40byte)就还可以写成calloc形式的:

int main()
{
    int* p = (int*)calloc(10, sizeof(int));
    if (p == NULL)
    {
        perror("p");
        return 0;
    }
//使用
    free(p);
    p = NULL;
    return 0;
}

细节:

同样的calloc也需要判断是否申请成功

用free释放申请好的空间(并且将其指针置为空)

image.png

通过对比上面两段代码我们可知:

malloc在申请往空间后不会对这部分空间有任何作业

calloc在申请完空间后会对这部分全部空间初始化为0

所以在我们使用时可以通过他们不同的类型进行选择使用

上面的malloc、calloc都是开辟内存的空间的,而下面的realloc就是修改开辟的内存空间

2.4realloc

知识点:

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

第一个参数是要调整的内存地址(由malloc、calloc、realloc开辟的内存块),第二个参数是所要调整的新的空间的大小(size个字节),返回情况是一样的

具体使用如下

int main()
{
  int* p = (int*)malloc(sizeof(int) * 5);
  if (p == NULL)
  {
    perror("malloc");
    return 1;
  }
  for (int i = 0; i < 5; i++)
  {
    *(p + i) = i + 1;
    printf("%d ", *(p + i));
  }
  int* ptr = (int *)realloc(p, sizeof(int) * 10);
  if(ptr != NULL)//当等于空指针时就没有动作了不用管ptr
  {
    p = ptr;
        ptr = NULL;
  }
  for (int i = 5; i < 10; i++)
  {
    *(p + i) = i + 1;
    printf("%d ", *(p + i));
  }
  free(p);
  p = NULL;
  return 0;
}

细节:

在上述代码中我们可以发现在用realloc增加或减少动态内存的空间后 返回时 接收返回值的指针是一个新的指针(int * ptr)而不是所要改变的那片空间的地址(p),这是因为:realloc开辟空间时分着两种情况:

1.当realloc所要开辟的空间在原始空间的后面有足够的空间让其使用时那就直接在原始位置处开辟

image.png

2.当realloc所要开辟的空间在原始位置后面没有足够的空间让其使用,此时就会寻找一个放的下的空间,并且将原数据拷贝过去,并且释放掉原空间和返回新的地址

image.png

3.就是因为可能的两种情况,所以为了避免第二种释放原位置返回新地址,所以用一个新指针接收其返回的地址,如果直接写成  p = realloc(p,sizeof(int)*10)的话 假如开辟没有成功那就会返回NULL而原本还有的5个空间大小直接就没了(p = NULL)。


当第一个参数传的是NULL时其用法和malloc类型开辟申请一块新的空间:

realloc(NULL,40) == malloc(40)  ;

并且返回指向这块空间的地址

相关文章
【C语言】内存布局大揭秘 ! -《堆、栈和你从未听说过的内存角落》
在C语言中,内存布局是程序运行时非常重要的概念。内存布局直接影响程序的性能、稳定性和安全性。理解C程序的内存布局,有助于编写更高效和可靠的代码。本文将详细介绍C程序的内存布局,包括代码段、数据段、堆、栈等部分,并提供相关的示例和应用。
121 5
【C语言】内存布局大揭秘 ! -《堆、栈和你从未听说过的内存角落》
C 语言在计算机科学中尤其在硬件交互方面占据重要地位。本文探讨了 C 语言与硬件交互的主要方法,包括直接访问硬件寄存器、中断处理、I/O 端口操作、内存映射 I/O 和设备驱动程序开发
C 语言在计算机科学中尤其在硬件交互方面占据重要地位。本文探讨了 C 语言与硬件交互的主要方法,包括直接访问硬件寄存器、中断处理、I/O 端口操作、内存映射 I/O 和设备驱动程序开发,以及面临的挑战和未来趋势,旨在帮助读者深入了解并掌握这些关键技术。
132 6
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
127 1
C语言及程序设计初步例程-4 C语言程序初体验
贺老师教学链接  C语言及程序设计初步 本课讲解 让程序会计算:求a和b两个数之和 #include &lt;stdio.h&gt; int main( ) { int a,b,sum; scanf("%d %d", &amp;a, &amp;b); sum=a+b; printf("%d\n", sum); return 0; } 用户界面友好(或罗
1106 0
《C语言及程序设计》实践项目——C语言程序初体验
返回:贺老师课程教学链接  C语言及程序设计初步   【项目1-输出点阵图】编一个程序,用你的姓名读音首字母,组成类似的趣图提示:printf("……\n");语句会输出双引号中的内容,'\n'完成换行[参考解答]【项目2-完成简单计算】(1)编程序,输入长方形的两边长a和b,输出长方形的周长和面积 提示:边长可以是整数也可以是小数;实现乘法的运算符是*[参考解答] (2)编程序,输入两个电
1299 0
【C语言程序设计——函数】素数判定(头歌实践教学平台习题)【合集】
本内容介绍了编写一个判断素数的子函数的任务,涵盖循环控制与跳转语句、算术运算符(%)、以及素数的概念。任务要求在主函数中输入整数并输出是否为素数的信息。相关知识包括 `for` 和 `while` 循环、`break` 和 `continue` 语句、取余运算符 `%` 的使用及素数定义、分布规律和应用场景。编程要求根据提示补充代码,测试说明提供了输入输出示例,最后给出通关代码和测试结果。 任务核心:编写判断素数的子函数并在主函数中调用,涉及循环结构和条件判断。
191 23
一文彻底搞清楚C语言的函数
本文介绍C语言函数:函数是程序模块化的工具,由函数头和函数体组成,涵盖定义、调用、参数传递及声明等内容。值传递确保实参不受影响,函数声明增强代码可读性。君志所向,一往无前!
35 1
一文彻底搞清楚C语言的函数
|
3月前
|
【C语言程序设计——函数】利用函数求解最大公约数和最小公倍数(头歌实践教学平台习题)【合集】
本文档介绍了如何编写两个子函数,分别求任意两个整数的最大公约数和最小公倍数。内容涵盖循环控制与跳转语句的使用、最大公约数的求法(包括辗转相除法和更相减损术),以及基于最大公约数求最小公倍数的方法。通过示例代码和测试说明,帮助读者理解和实现相关算法。最终提供了完整的通关代码及测试结果,确保编程任务的成功完成。
169 15
|
3月前
|
【C语言程序设计——函数】亲密数判定(头歌实践教学平台习题)【合集】
本文介绍了通过编程实现打印3000以内的全部亲密数的任务。主要内容包括: 1. **任务描述**:实现函数打印3000以内的全部亲密数。 2. **相关知识**: - 循环控制和跳转语句(for、while循环,break、continue语句)的使用。 - 亲密数的概念及历史背景。 - 判断亲密数的方法:计算数A的因子和存于B,再计算B的因子和存于sum,最后比较sum与A是否相等。 3. **编程要求**:根据提示在指定区域内补充代码。 4. **测试说明**:平台对代码进行测试,预期输出如220和284是一组亲密数。 5. **通关代码**:提供了完整的C语言代码实现
90 24
|
3月前
|
【C语言程序设计——函数】递归求斐波那契数列的前n项(头歌实践教学平台习题)【合集】
本关任务是编写递归函数求斐波那契数列的前n项。主要内容包括: 1. **递归的概念**:递归是一种函数直接或间接调用自身的编程技巧,通过“俄罗斯套娃”的方式解决问题。 2. **边界条件的确定**:边界条件是递归停止的条件,确保递归不会无限进行。例如,计算阶乘时,当n为0或1时返回1。 3. **循环控制与跳转语句**:介绍`for`、`while`循环及`break`、`continue`语句的使用方法。 编程要求是在右侧编辑器Begin--End之间补充代码,测试输入分别为3和5,预期输出为斐波那契数列的前几项。通关代码已给出,需确保正确实现递归逻辑并处理好边界条件,以避免栈溢出或结果
190 16