C语言进阶第九课 --------动态内存管理

作者前言

🎂 ✨✨✨✨✨✨🍧🍧🍧🍧🍧🍧🍧🎂
​🎂 作者介绍: 🎂🎂
🎂 🎉🎉🎉🎉🎉🎉🎉 🎂
🎂作者id:老秦包你会, 🎂
简单介绍:🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂
喜欢学习C语言和python等编程语言,是一位爱分享的博主,有兴趣的小可爱可以来互讨 🎂🎂🎂🎂🎂🎂🎂🎂
🎂个人主页::小小页面🎂
🎂gitee页面:秦大大🎂
🎂🎂🎂🎂🎂🎂🎂🎂
🎂 一个爱分享的小博主 欢迎小可爱们前来借鉴🎂


动态内存

  • **作者前言**
  • 动态内存的意义
  • 动态内存函数的介绍
    • malloc
    • free
    • calloc
    • realloc
  • 常见的动态内存错误
    • 对NULL指针的解引用操作
    • 对动态内存的越界访问
    • 对非动态开辟的内存进行free释放
    • 使用free释放动态开辟的内存的一部分
    • 对一块空间多次释放
    • 动态开辟内存忘记释放(内存泄漏)
  • 经典笔试题
    • C/C++中程序内存区域分布
  • 柔性数组
    • 柔性数组的特点
    • 使用柔性数组的好处
  • 总结

动态内存的意义

在我们空间的开辟有定义变量

#include<stdio.h>
int main()
{int a = 0;char arr[] = "sdsd";printf("%p\n", &a);printf("%p\n", arr);return 0;
}

甚至还有局部变量都会向内存申请空间,但是这些空间不擦长久,或者很难随意操作这些空间进行销毁,或者不能随意扩大空间
但是上述的开辟空间的方式有两个特点:

  1. 空间开辟大小是固定的。
  2. 数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配。

动态内存函数的介绍

malloc

在这里插入图片描述
这个函数的动态内存开辟函数,
size_t是一个无符号整数类型,表示需要分配的内存大小(以字节为单位)。malloc函数会在堆上分配一块指定大小的内存,并返回指向该内存块的指针。如果分配失败,则返回NULL。

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
int main()
{int* arr = (int*)malloc(sizeof(int) * INT_MAX);if (arr == NULL){perror("malloc:");return 1;}return 0;
}

可能有一些人会利用malloc申请0个字节,这种做法本身没有错误,但是这个就是脱裤子放屁多此一举
如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。
在这里插入图片描述

free

有心细的小可爱就会发现,当我们程序运行结束就会释放出开辟的动态内存(被动),如果我们要自己释放就要用到free函数
在这里插入图片描述
free是C语言中用于释放动态分配内存的函数。当我们使用malloc、calloc等函数在程序运行期间动态申请内存时,需要在使用完毕后使用free函数将其释放回系统,以便其他程序或进程可以使用。

其中,ptr是指向需要释放的内存块的指针。使用free函数可以将之前通过malloc、calloc等函数动态分配的内存块释放回系统,以避免内存泄漏和浪费。

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
int main()
{int* arr = (int*)malloc(sizeof(int) * 10);if (arr == NULL){perror("malloc");return 1;}int i = 0;for (i = 0; i < 10; i++){arr[i] = i;printf("%d ", arr[i]);}free(arr);arr = NULL;return 0;
}

需要注意的是,在使用free函数释放内存块时需要注意以下几点:

只能释放之前通过malloc、calloc等函数动态分配的内存块,不能释放栈上的局部变量和全局变量等静态分配的内存;
不能重复释放同一块内存,否则会导致程序崩溃;
释放内存后,应该将指针设置为NULL,以避免出现野指针问题。

calloc

在这里插入图片描述
calloc是C语言中用于动态分配内存并进行初始化的函数。与malloc函数相似,calloc也用于在程序运行时动态分配内存,但与malloc不同的是,calloc会将分配的内存块初始化为零,而malloc不会
其中,num表示需要分配的元素个数,size表示每个元素的大小。calloc函数会分配num个大小为size的连续内存块,并将其初始化为零。如果分配成功,则返回指向分配内存块的指针;如果分配失败,则返回NULL。

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
int main()
{int* arr = (int*)calloc(10, sizeof(int));if (arr == NULL){perror("calloc");return 1;}int i = 0;for (i = 0; i < 10; i++){arr[i] = i;}free(arr);arr = NULL;return 0;
}

realloc

realloc函数的出现让动态内存管理更加灵活,前面的malloc和calloc申请的空间无法避免申请空间过大或者过小,realloc函数就可以做到动态开辟内存的大小进行调整

realloc是C语言中用于重新分配内存的函数。在程序运行期间,我们有时需要重新分配内存块的大小,可以使用realloc函数来实现。realloc函数可以将之前分配的内存块的大小调整为新的大小,并返回指向新内存块的指针。
在这里插入图片描述
ptr是指向之前分配的内存块的指针,size是需要重新分配的内存块的大小。
如果重新分配成功,则返回指向新内存块的指针;如果分配失败,则返回NULL。需要注意的是,如果新的大小小于原来的大小,则realloc函数会截断原来的内存块;如果新的大小大于原来的大小,则realloc函数会在原来的内存块后面分配新的内存块。

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
int main()
{int* arr = (int*)malloc(sizeof(int) * 10);int* arr1 = (int*)calloc(20, sizeof(int));if (arr == NULL){perror("malloc");}if (arr1 == NULL){perror("calloc");}//调整空间int *arr3 = (int*)realloc(arr, 2000 * sizeof(int));if (arr3 != NULL){arr = arr3;}free(arr);free(arr1);arr = NULL;arr1 = NULL;arr3 = NULL;return 0;
}

realloc函数开辟的空间情况分两种
第一种:
在这里插入图片描述
之前malloc开辟空间的后面刚好满足扩大后的空间大小就会在原来的地址处重新扩大,这种情况返回的新地址就会和旧地址的值是一样的
在这里插入图片描述

第二种:

之前malloc开辟的空间的后面不能满足扩大后的空间大小,就会放弃在原来的空间扩大,反而会重新找一块能满足条件的内存进行开辟,一次性开辟好,会将旧的空间的数据就会拷贝到新的空间,旧的空间就会销毁
在这里插入图片描述

常见的动态内存错误

对NULL指针的解引用操作

这种情况的出现是因为没有对返回值进行判断造成的

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
int main()
{int* arr = malloc(sizeof(int) * INT_MAX);int i = 0;for (i = 0; i < INT_MAX; i++){arr[i] = 1;}return 0;
}

如上面代码一样,没有判断是否创建成功,直接解引用操作就会报错
在这里插入图片描述

对动态内存的越界访问

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
int main()
{int* arr = (int*)calloc(10, sizeof(int));if (arr == NULL){perror("calloc");return 1;}int i = 0;for (i = 0; i <= 10; i++){arr[i] = i;}for (i = 0; i <= 10; i++){printf("%d ", *(arr + i));}free(arr);arr = NULL;return 0;
}

这种情况就和我们创建数组,越界访问是一样的

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

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
int main()
{int a = 0;int* p = &a;free(p);p = NULL;return 0;
}

这种情况就是懵了

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

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
int main()
{int* arr = calloc(10, sizeof(int));int i = 0; for (i = 0; i < 5; i++){*arr = i;arr++;}free(arr);arr = NULL;return 0;
}

在这里插入图片描述

对一块空间多次释放

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
int main()
{int* arr = (int*)malloc(sizeof(int) * 10);if (arr == NULL){perror("malloc");return 1;}free(arr);free(arr);return 0;
}

解决这个错误我们可以养成一个习惯,只要释放过的空间,对应的变量全部赋值为NULL,因为free释放NULL不会有啥错误

动态开辟内存忘记释放(内存泄漏)

include <stdio.h>
#include <stdlib.h>
#include <limits.h>
void Other()
{int* arr = (int*)malloc(sizeof(int) * 10);
}
int main()
{Other();while (1){;}return 0;
}

这样很容易造成空间不足,我们要记住谁申请,谁释放,如果没有释放交接任务也要告诉别人释放

经典笔试题

第一题

void GetMemory(char *p)
{p = (char *)malloc(100);
}
void Test(void)
{char *str = NULL;GetMemory(str);strcpy(str, "hello world");printf(str);
}

上面这些代码主要的错误就是当我们调用Test函数时,也会调用到 GetMemory函数,而传参传递的时变量str的值,而不是地址,最后str的值还是NULL,还造成了内存泄漏对NULL解引用就会报错,如果把 GetMemory函数更改为 GetMemory(char* *p ),然后传递str的地址
更改后:

void GetMemory(char* *p)
{*p = (char*)malloc(100);
}
void Test(void)
{char* str = NULL;GetMemory(&str);strcpy(str, "hello world");printf(str);free(str);str = NULL;
}
int main()
{Test();return 0;
}

还要注意的printf函数的使用没有错,是可以这样写的,这个写法只针对字符串
第二题

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
char* GetMemory(void)
{char p[] = "hello world";return p;
}
void Test(void)
{char* str = NULL;str = GetMemory();printf(str);
}
int main()
{Test();return 0;
}

这里的错误是GetMemory函数在调用结束后,p的空间销毁掉了,返回的值也就成了野指针
第三题

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include<string.h>
void GetMemory(char** p, int num)
{*p = (char*)malloc(num);
}
void Test(void)
{char* str = NULL;GetMemory(&str, 100);strcpy(str, "hello");printf(str);
}
int main()
{Test();return 0;
}

这道题主要是少了free释放,内存泄漏了
第四题

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include<string.h>
void Test(void)
{char* str = (char*)malloc(100);strcpy(str, "hello");free(str);if (str != NULL){strcpy(str, "world");printf(str);}
}
int main()
{Test();return 0;
}

这里的错误就是野指针,在我们释放了动态开辟的内存,str还是指向那块地址,这样就会非法访问内存,这里我们要注意释放后一定要赋值NULL

C/C++中程序内存区域分布

在这里插入图片描述
其中代码段是用来存储代码翻译成的二进制指令只读常量(字符串常量),里面的数据不可更改
在这里插入图片描述
这个图就可以很明了的知道动态内存开辟的空间是在堆区,局部变量和形式参数的空间是在栈区m全局变量和静态变量的空间是在数据段(静态区)
还要注意的是函数的空间也是在栈上创建的,函数栈帧的创建和销毁前面我已经写过,可以去看看
C/C++程序内存分配的几个区域:

  1. 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结
    束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是
    分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返
    回地址等。
  2. 堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分
    配方式类似于链表。
  3. 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。
  4. 代码段:存放函数体(类成员函数和全局函数)的二进制代码。

柔性数组

这个概念是在C99引入的,之前是没有的
形成柔性数组的条件:

  1. 要在结构体中
  2. 是结构体的最后一个成员,并且这个数组是一个未知大小的数组(变长数组)

写法:

struct S{int a;int arr[];//柔性数组}a1;struct S1{int a;int arr[0];//柔性数组}a2;

代码中 的两个结构体属于两种写法,

柔性数组的特点

  1. 结构中的柔性数组成员前面必须至少一个其他成员。
  2. sizeof 返回的这种结构大小不包括柔性数组的内存。
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include<string.h>
struct S1
{int a;int arr[0];
};
int main()
{printf("%d", sizeof(struct S1);return 0;
}

在这里插入图片描述
可以看到sizeof返回的大小不包含柔性数组,假设结构体只有一个成员,而这个成员是柔性数组就会发现这个结构体的大小是0


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

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include<string.h>
struct S1
{int a;int arr[0];
};
int main()
{struct S1* p = (struct S1*)malloc(sizeof(int) + 20);if (p == NULL){perror("malloc");return;}p->a = 10;int i = 0;for (i = 0; i < 4; i++){p->arr[i] = i;}free(p);p = NULL;return 0;
}

其实我们可以不使用柔性数组,我们也可以模拟出来

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include<string.h>
struct S1
{int a;int arr[0];
};
struct S2
{int a;int* arr;
};
int main()
{/*struct S1* p = (struct S1*)malloc(sizeof(int) + 20);if (p == NULL){perror("malloc");return;}p->a = 10;int i = 0;for (i = 0; i < 4; i++){p->arr[i] = i;}free(p);p = NULL;*/struct S2 a2;a2.a = 10;a2.arr = (int*)malloc(sizeof(int) * 4);if (a2.arr == NULL){perror("malloc");return;}printf("%d", sizeof(struct S2));free(a2.arr);a2.arr = NULL;return 0;
}

使用这种方法虽然可以代替柔性数组,但是使用free的次数会增多,而使用柔性数组只需使用一次free

当我们使用malloc开辟的数次越多,产生的内存碎片就会越多,内存的利用率就会下降

使用柔性数组的好处

第一个好处是:方便内存释放
如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把整个结构体返回给用户。用户调用free可以释放结构体,但是用户并不知道这个结构体内的成员也需要free,所以你不能指望用户来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存一次性分配好了,并返回给用户一个结构体指针,用户做一次free就可以把所有的内存也给释放掉。
第二个好处是:这样有利于访问速度.
连续的内存有益于提高访问速度,也有益于减少内存碎片。(其实,我个人觉得也没多高了,反正你跑不了要用做偏移量的加法来寻址)

总结

在这里介绍了动态内存的开辟、常见错误柔性数组,有不懂的小可爱可以私聊

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/170499.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python武器库开发-面向对象篇(六)

面向对象篇(六) 多态 多态指的是一类事物有多种形态&#xff0c;一个抽象类有多个子类&#xff08;因而多态的概念依赖于继承&#xff09;&#xff0c;不同的子类对象调用相同的方法&#xff0c;产生不同的执行结果&#xff0c;多态可以增加代码的灵活度 实现多态的步骤&…

物联网知识复习

物联网的内涵和体系结构 物联网的基本内涵 物联网的基本内涵在于物联&#xff0c;物物相连或者物和人相连的互联网。 也就是说&#xff0c;它是要由物主动发起的&#xff0c;物物互联的互联网。 它的第一层意思是说物和物相连&#xff1b;第二层意思是说物和人相连。 物联网的…

mac上mongodb 以及可视化工具 下载以及安装

简介 1. 下载 官网上的下载地址藏得非常深&#xff0c;不花老半天 根本找不到 下载地址 https://www.mongodb.com/try/download/community 目前最新社区版本7.0.2 下载链接 mac intel芯片 &#xff1a; https://fastdl.mongodb.org/osx/mongodb-macos-x86_64-7.0.2.tgz ma…

VR全景应用广泛体现在哪里?有何优势?

VR全景作为一种新型营销方式&#xff0c;正在逐渐走进人们的视线&#xff0c;它区别于以往单一角度的照片和视频&#xff0c;VR全景制作显得更加直观、更加真实、更加生动。VR全景通过VR技术将所拍摄的图片变成720度可观看的场景模式&#xff0c;把产品的特色以及魅力整体呈现展…

css中px、em、rem、%、vw、vh、vm、rpx 这些单位的区别

序言 px&#xff1a;像素 相对长度单位&#xff0c;相对于显示器屏幕分辨率(推荐使用) em&#xff1a;相对长度单位 基准点为父节点字体的大小&#xff0c;如果自身定义了font-size按自身来计算&#xff08;浏览器默认字体是16px&#xff09;&#xff0c;整个页面内1em不是一个…

Origami Studio for Mac:塑造未来,掌握原型设计之巅

在当今高度竞争的设计领域&#xff0c;原型设计的重要性不言而喻。它不仅是沟通想法&#xff0c;也是测试和改进设计的关键环节。而现在&#xff0c;一款强大的原型设计工具——Origami Studio for Mac&#xff0c;正在席卷设计界&#xff0c;以其独特的功能和卓越的性能&#…

【java学习—九】类的成员之四:初始化块(1)

文章目录 1. 初始化块(代码块)的作用2. 静态代码块3. 非静态代码块和静态代码块的特点 1. 初始化块(代码块)的作用 作用&#xff1a;对java对象进行初始化      程序执行的顺序&#xff1a;     ①声明成员变量的默认值 --> ②显式初始化、多个初始化块依次被执行&a…

【29】c++设计模式——>策略模式

策略模式 C中的策略模式&#xff08;Strategy Pattern&#xff09;是一种行为型设计模式&#xff0c;它允许在运行时选择算法的行为。策略模式通过将算法封装成独立的类&#xff0c;并且使它们可以互相替换&#xff0c;从而使得算法的变化独立于使用算法的客户端。 策略模式通…

天锐绿盾加密软件——企业数据透明加密、防泄露系统

天锐绿盾是一种企业级数据透明加密、防泄密系统&#xff0c;旨在保护企业的核心数据&#xff0c;防止数据泄露和恶意攻击。它采用内核级透明加密技术&#xff0c;可以在不影响员工正常工作的前提下&#xff0c;对需要保护的数据进行加密操作。 PC访问地址&#xff1a; https:/…

C语言之数组

目录 一维数组的定义和使用 二维数组的定义和使用 字符数组和字符串 练习题 练习一 练习二 练习三 一维数组的定义和使用 当涉及到一系列相同类型的数据时&#xff0c;C语言中的一维数组是一种非常有用的数据结构。以下是关于C语言一维数组的定义和使用的详细说明&…

淘宝app商品详情源数据API接口(解决滑块问题)可高并发采集

通过API接口采集淘宝商品列表和app商品详情遇到滑块验证码的解决方法&#xff08;带SKU和商品描述&#xff0c;支持高并发&#xff09;&#xff0c;主要是解决了高频情况下的阿里系滑块和必须要N多小号才能解决的反扒问题&#xff0c;以后都可以使用本方法&#xff1a; 大家都…

从顺序表表中删除具有最小值的元素(假设不唯一)并由函数返回被删元素值

从顺序表表中删除具有最小值的元素&#xff08;假设不唯一&#xff09;&#xff0c;并由函数返回被删元素值 空出的位置由最后一个元素填补&#xff0c;若顺序表为空&#xff0c;则显示出错误信息并退出运行 //顺序表定义及初始化 #define _CRT_SECURE_NO_WARNINGS #include&l…

HarmonyOS 音频开发指导:使用 OpenSL ES 开发音频播放功能

OpenSL ES 全称为 Open Sound Library for Embedded Systems&#xff0c;是一个嵌入式、跨平台、免费的音频处理库。为嵌入式移动多媒体设备上的应用开发者提供标准化、高性能、低延迟的 API。HarmonyOS 的 Native API 基于Khronos Group开发的OpenSL ES 1.0.1 API 规范实现&am…

【机器学习合集】标准化与池化合集 ->(个人学习记录笔记)

文章目录 标准化与池化1. 标准化/归一化1.1 归一化归一化的作用 1.2 标准化批标准化方法 Batch Normailzation标准化方法的对比自动学习标准化方法 2. 池化2.1 池化的作用2.2 常见的池化方法2.3 池化方法的差异2.4 池化的必要性 标准化与池化 1. 标准化/归一化 1.1 归一化 归…

拦截器以及统一功能的实现

目录 引言 实现一个简单的拦截器 拦截器小结 统一访问前缀 统一异常处理 统一返回参数 ControllerAdvice 引言 HandlerInterceptor是Spring MVC框架提供的一个拦截器接口&#xff0c;它用于对请求进行拦截和处理。在Spring MVC中&#xff0c;拦截器可以用于实现一些通用的功能…

C#调用C++ 的DLL传送和接收中文字符串

1 c#向c传送中文字符串 设置&#xff1a;将 字符集 改为 使用多字节字符集 cpp代码&#xff1a; extern "C"_declspec(dllexport) int input_chn_str(char in_str[]) {cout<<in_str<<endl;return 0; }c#代码&#xff1a; [DllImport("Demo.dll…

Kmssink插件添加缩放显示功能的分析思路与具体实现

XILINX MPSOC 实现输出缩放&#xff0c;PL一侧的配置如下&#xff1a; 修改PL侧的显示通道流程为&#xff1a;DDR -> FRAMBUF_RD -> VPSS(SCALE) -> V_MIX -> HDMI_TX -> MONITOR , 通过设置HDMI_TX的宽高&#xff0c;利用xlnx_bridge 接口关联设置VPSS(scal…

微信小程序设置 wx.showModal 提示框中 确定和取消按钮的颜色

wx官方提供的 showModal 无疑是个非常优秀的选择提示工具 但是 我们还可以让他的颜色更贴近整体的小程序风格 cancelColor 可以改变取消按钮的颜色 confirmColor 则可以控制确定按钮的颜色 参考代码如下 wx.showModal({cancelColor: #0000FF,confirmColor: #45B250,content:…

2023年CSP-S赛后总结

目录 T1 题目描述 输入格式 输出格式 T2 题目描述 输入格式 输出格式 题目描述 输入格式 输出格式 题意翻译 T3 题目背景 题目描述 输入格式 输出格式 T4 题目描述 输入格式 输出格式 总结 T1 题目描述 小 Y 有一把五个拨圈的密码锁。如图所示&#xff0…