进阶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)  ;

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

相关文章
|
7月前
|
存储 自然语言处理 编译器
【C语言】编译与链接:深入理解程序构建过程
【C语言】编译与链接:深入理解程序构建过程
132 1
|
6月前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
152 5
|
6月前
|
C语言
C语言编程中,错误处理至关重要,能提升程序的健壮性和可靠性
C语言编程中,错误处理至关重要,能提升程序的健壮性和可靠性。本文探讨了C语言中的错误类型(如语法错误、运行时错误)、基本处理方法(如返回值、全局变量、自定义异常处理)、常见策略(如检查返回值、设置标志位、记录错误信息)及错误处理函数(如perror、strerror)。强调了不忽略错误、保持处理一致性及避免过度处理的重要性,并通过文件操作和网络编程实例展示了错误处理的应用。
165 4
|
6月前
|
网络协议 物联网 数据处理
C语言在网络通信程序实现中的应用,介绍了网络通信的基本概念、C语言的特点及其在网络通信中的优势
本文探讨了C语言在网络通信程序实现中的应用,介绍了网络通信的基本概念、C语言的特点及其在网络通信中的优势。文章详细讲解了使用C语言实现网络通信程序的基本步骤,包括TCP和UDP通信程序的实现,并讨论了关键技术、优化方法及未来发展趋势,旨在帮助读者掌握C语言在网络通信中的应用技巧。
129 2
|
6月前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
139 1
|
6月前
|
程序员 C语言
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门。本文深入探讨了指针的基本概念、声明方式、动态内存分配、函数参数传递、指针运算及与数组和函数的关系,强调了正确使用指针的重要性,并鼓励读者通过实践掌握这一关键技能。
132 1
|
7月前
|
存储 文件存储 C语言
深入C语言:文件操作实现局外影响程序
深入C语言:文件操作实现局外影响程序
|
3月前
|
编译器 C++ 开发者
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
|
1月前
|
编译器 C++ 容器
【c++11】c++11新特性(上)(列表初始化、右值引用和移动语义、类的新默认成员函数、lambda表达式)
C++11为C++带来了革命性变化,引入了列表初始化、右值引用、移动语义、类的新默认成员函数和lambda表达式等特性。列表初始化统一了对象初始化方式,initializer_list简化了容器多元素初始化;右值引用和移动语义优化了资源管理,减少拷贝开销;类新增移动构造和移动赋值函数提升性能;lambda表达式提供匿名函数对象,增强代码简洁性和灵活性。这些特性共同推动了现代C++编程的发展,提升了开发效率与程序性能。
58 12
|
2月前
|
设计模式 安全 C++
【C++进阶】特殊类设计 && 单例模式
通过对特殊类设计和单例模式的深入探讨,我们可以更好地设计和实现复杂的C++程序。特殊类设计提高了代码的安全性和可维护性,而单例模式则确保类的唯一实例性和全局访问性。理解并掌握这些高级设计技巧,对于提升C++编程水平至关重要。
60 16