零基础自学C语言|动态内存管理

✈为什么要有动态内存分配

我们已经掌握的内存开辟方式有:

	int a = 0;//在栈空间上开辟四个字节char arr[10] = { 0 };//在栈空间上开辟10个字节的连续空间

但是上述的开辟空间的方式有两个特点:

  • 空间开辟大小是固定的。
  • 数组在申明的时候,必须指定数组的长度,数组空间一旦确定了大小不能调整。

但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知
道,那数组的编译时开辟空间的方式就不能满足了。

C语言引入了动态内存开辟,让程序员自己可以申请和释放空间,就比较灵活了。

✈malloc和free

🚀malloc

C语言提供了一个动态内存开辟的函数:

void* malloc(size_t size);

这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。

  • 如果开辟成功,则返回一个指向开辟好空间的指针。
  • 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
  • 返回值的类型是void*,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。
  • 如果参数size为0,malloc的行为是标准是未定义的,取决于编译器。

应用举例:

#include<stdio.h>
#include<stdlib.h>int main()
{int num = 10;int* parr = (int*)malloc(num * sizeof(int));if (parr==NULL)//判断是否开辟成功{perror("malloc");return 1;}int i = 0;//实现数组功能for (i = 0; i < num; i++){*(parr + i) = i;}for (i = 0; i < num; i++){printf("%d ", *(parr + i));}return 0;
}

运行效果:

🚀free

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

void free(void* ptr);

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

  • 如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
  • 如果参数 ptr是NULL指针,则函数什么事都不做。
  • malloc和free都声明在stdlib.h头文件中。

举个例子:

#include<stdio.h>
#include<stdlib.h>int main()
{int num = 10;int* parr = (int*)malloc(num * sizeof(int));if (parr==NULL)//判断是否开辟成功{perror("malloc");return 1;}int i = 0;//实现数组功能for (i = 0; i < num; i++){*(parr + i) = i;}for (i = 0; i < num; i++){printf("%d ", *(parr + i));}free(parr);//有这个必要吗?parr = NULL;return 0;
}

我们在开辟空间时,务必要记得释放,否则可能导致内存泄漏,导致代码运行一段时间后内存不足。同时,要把指针置为空指针,避免出现野指针的问题。

✈calloc和realloc

🚀calloc

C语言还提供了一个函数叫 calloc,calloc 函数也用来动态内存分配。原型如下:

void* calloc(size_t num, size_t size);

函数的功能是为 num 个大小为size 的元素开辟一块空间,并且把空间的每个字节初始化为0。与函数 malloc的区别只在于calloc会在返回地址之前把申请的空间的每个字节初始化为全举不例子:

int main()
{int num = 10;int* parr1 = (int*)malloc(num * sizeof(int));if (parr1 == NULL){perror("malloc");return 1;}int* parr2 = (int*)calloc(num , sizeof(int));if (parr2 == NULL){perror("calloc");return 1;}int i = 0;for (i = 0; i < 10; i++){printf("%d ", *(parr1 + i));}printf("\n");for (i = 0; i < 10; i++){printf("%d ", *(parr2 + i));}return 0;
}

运行结果:

可见,malloc开辟的空间中的值为随机值,但是,calloc会初始化为0。

所以如果我们对申请的内存空间的内容要求初始化,那么可以很方便的使用calloc函数来完成任务。

🚀realloc

realloc函数的出现让动态内存管理更加灵活。
有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时候内存,我们一定会对内存的大小做灵活的调整。那realloc函数就可以做到对动态开辟内存大小的调整。
函数原型如下:

void* realloc(void* ptr, size_t size);
  • ptr是要调整的内存地址
  • size调整之后新大小
  • 返回值为调整之后的内存起始位置。
  • 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间。

realloc在调整内存空间的是存在两种情况:

情况1:原有空间之后有足够大的空间
情况2:原有空间之后没有足够大的空间

情况1
当是情况1的时候,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化。
情况2
当是情况2的时候,原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另找一个合适大小
的连续空间来使用。这样函数返回的是一个新的内存地址。
由于上述的两种情况,realloc函数的使用就要注意一些。

#include <stdio.h>
#include <stdlib.h>
int main()
{int* ptr = (int*)malloc(100);if (ptr != NULL){//业务处理}else{return 1;}//扩展容量//代码1 - 直接将realloc的返回值放到ptr中ptr = (int*)realloc(ptr, 1000);//这样可以吗?(如果申请失败会如何?)//代码2 - 先将realloc函数的返回值放在p中,不为NULL,在放ptr中int* p = NULL;p = realloc(ptr, 1000);if (p != NULL)		{ptr = p;}//业务处理free(ptr);return 0;
}

✈常见的动态内存的错误

🚀解引用空指针

void test()
{int* p = (int*)malloc(INT_MAX / 4);*p = 20;//如果p的值是NULL,就会有问题free(p);
}

🚀越界访问

void test()
{int i = 0;int* p = (int*)malloc(10 * sizeof(int));if (NULL == p){exit(EXIT_FAILURE);}for (i = 0; i <= 10; i++){*(p + i) = i;//当i是10的时候越界访问}free(p);
}

🚀对非动态开辟空间使用free释放

void test()
{int a = 10;int* p = &a;free(p);//ok?
}

🚀使用free释放动态开辟空间的一部分

void test(){int *p = (int *)malloc(100);p++;free(p);//p不再指向动态内存的起始位置}

🚀对同一块空间多次释放


void test()
{int* p = (int*)malloc(100);free(p);free(p);//重复释放
}

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

void test()
{int* p = (int*)malloc(100);if (NULL != p){*p = 20;}
}
int main()
{test();while (1);
}

切记动态开辟空间一定要正确释放。

✈经典笔试题解析

🚀题目1:

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

运行后,由于p是str的一份临时拷贝,所以str还是空指针。而空指针不能进行解引用。

🚀题目2:

char* GetMemory(void)
{char p[] = "hello world";return p;
}
void Test(void)
{char* str = NULL;str = GetMemory();printf(str);
}

因为p的内存已经被释放,所以运行结果是乱码。

🚀题目3:


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

释放的是p的内存,而str不改变。

🚀题目4:

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

打印world。

✈柔性数组

也许你从来没有听说过柔性数组(flexible array)这个概念,但是它确实是存在的。C99中,结构中的最后一个元素允许是未知大小的数组,这就叫做“柔性数组”成员。

例如:


struct Test
{char a;int b;int a[0];
};


 

🚀柔性数组的特点:

  • 结构中的柔性数组成员前面必须至少一个其他成员。
  • sizeof返回的这种结构大小不包括柔性数组的内存。

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

struct Test
{char a;int b;int a[0];
};int main()
{printf("%d", sizeof(struct Test));return 0;
}

运行结果是8。

🚀柔性数组的使用

//代码1 
#include <stdio.h>
#include <stdlib.h>
int main()
{int i = 0;type_a* p = (type_a*)malloc(sizeof(type_a) + 100 * sizeof(int));//业务处理p->i = 100;for (i = 0; i < 100; i++){p->a[i] = i;}free(p);return 0;
}

这样柔性数组成员a,相当于获得了100个整型元素的连续空间。

🚀柔性数组的优势

上述的type_a结构也可以设计为下面的结构,也能完成同样的效果

//代码2 #include <stdio.h>
#include <stdlib.h>
typedef struct st_type
{int i;int* p_a;
}type_a;
int main()
{type_a* p = (type_a*)malloc(sizeof(type_a));p->i = 100;p->p_a = (int*)malloc(p->i * sizeof(int));//业务处理for (i = 0; i < 100; i++){p->p_a[i] = i;}//释放空间free(p->p_a);p->p_a = NULL;free(p);p = NULL;return 0;
}

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

✈总结C/C++中程序内存区域划分

C/C++程序内存分配的几个区域:

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

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

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

相关文章

【开源】SpringBoot框架开发公司货物订单管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 客户管理模块2.2 商品维护模块2.3 供应商管理模块2.4 订单管理模块 三、系统展示四、核心代码4.1 查询供应商信息4.2 新增商品信息4.3 查询客户信息4.4 新增订单信息4.5 添加跟进子订单 五、免责说明 一、摘要 1.1 项目…

Cap2:Pytorch转TensorRT(上:Pytorch->ONNX)

文章目录 1、pytorch导出onnx模型2、使用onnxruntime推理onnx模型3、精度对齐4、总结 深度学习框架种类繁多&#xff0c;想实现任意框架之间的模型转换是一件困难的事情。但现在有一个中间格式ONNX&#xff0c;任何框架模型都支持转为ONNX&#xff0c;然后也支持从ONNX转为自身…

案例分析篇00-【历年案例分析真题考点汇总】与【专栏文章案例分析高频考点目录】(2024年软考高级系统架构设计师冲刺知识点总结-案例分析篇-先导篇)

专栏系列文章&#xff1a; 2024高级系统架构设计师备考资料&#xff08;高频考点&真题&经验&#xff09;https://blog.csdn.net/seeker1994/category_12593400.html 案例分析篇01&#xff1a;软件架构设计考点架构风格及质量属性 案例分析篇11&#xff1a;UML设计考…

疫情网课管理系统|基于springboot框架+ Mysql+Java+Tomcat的疫情网课管理系统设计与实现(可运行源码+数据库+设计文档+部署说明)

推荐阅读100套最新项目 最新ssmjava项目文档视频演示可运行源码分享 最新jspjava项目文档视频演示可运行源码分享 最新Spring Boot项目文档视频演示可运行源码分享 目录 前台功能效果图 ​编辑 学生功能模块 管理员功能 教师功能模块 系统功能设计 数据库E-R图设计 lun…

Ubuntu上搭建TFTP服务

Ubuntu上搭建TFTP服务 TFTP服务简介搭建TFTP服务安装TFTP服务修改配置文件 重启服务 TFTP服务简介 TFTP是一个基于UDP协议实现的用于在客户机和服务器之间进行简单文件传输的协议&#xff0c;适用于开销不大、不复杂的应用场合。TFTP协议专门为小文件传输而设计&#xff0c;只…

虚拟游戏理财 - 华为OD统一考试(C卷)

OD统一考试&#xff08;C卷&#xff09; 分值&#xff1a; 100分 题解&#xff1a; Java / Python / C 题目描述 在一款虚拟游戏中生活&#xff0c;你必须进行投资以增强在虚拟游戏中的资产以免被淘汰出局。 现有一家Bank&#xff0c;它提供有若干理财产品m&#xff0c;风险及…

【PHP安全】PHP伪协议

PHP伪协议&#xff1a; file:// #访问本地文件系统http:// #访问HTTPs网址ftp:// #访问ftp URLphp:// #访问输入输出流zlib:// #压缩流data:// #数据&#xff08;RFC 2397&#xff09;ssh2:// #security shell2expect:// #处理交互式的流glob:// #查找匹配的文件路径phar:// #P…

Siamese Network(孪生神经网络)详解

Siamese和Chinese有点像。Siam是古时候泰国的称呼&#xff0c;中文译作暹罗。Siamese也就是“暹罗”人或“泰国”人。Siamese在英语中是“孪生”、“连体”的意思&#xff0c;这是为什么呢&#xff1f;十九世纪泰国出生了一对连体婴儿&#xff0c;当时的医学技术无法使两人分离…

软件功能测试内容有哪些?湖南长沙软件测评公司分享

软件功能测试主要是验证软件应用程序的功能&#xff0c;且不管功能是否根据需求规范运行。是通过给出适当的输入值&#xff0c;确定输出并使用预期输出验证实际输出来测试每个功能。也可以看作“黑盒测试”&#xff0c;因为功能测试不用考虑程序内部结构和内部特性&#xff0c;…

Orange3数据预处理(清理特征组件)

清理特征 移除未使用的属性值和无用的属性&#xff0c;并对剩余的值进行排序。 输入 数据: 输入数据集 输出 数据: 过滤后的数据集 命名属性定义有时包含在数据中不出现的值。即使原始数据中没有这种情况&#xff0c;数据过滤、选择示例子集等操作也可能移除…

(二十五)Flask之MTVMVC架构模式Demo【重点:原生session使用及易错点!】

目录&#xff1a; 每篇前言&#xff1a;MTV&MVC构建一个基于MTV模式的Demo项目&#xff1a;蹦出一个问题&#xff1a; 每篇前言&#xff1a; &#x1f3c6;&#x1f3c6;作者介绍&#xff1a;【孤寒者】—CSDN全栈领域优质创作者、HDZ核心组成员、华为云享专家Python全栈领…

关于Transfomer的思考

为何诞生 在说transformer是什么&#xff0c;有什么优势之类的之前&#xff0c;先谈一谈它因何而诞生。transformer诞生最重要的原因是早先的语言模型&#xff0c;比如RNN&#xff0c;由于其本身的训练机制导致其并行度不高&#xff0c;特别是遇到一些长句子的情况下。其次&…

抖音开放平台第三方开发,实现代小程序备案申请

大家好&#xff0c;我是小悟 抖音小程序备案整体流程总共分为五个环节&#xff1a;备案信息填写、平台初审、工信部短信核验、通管局审核和备案成功。 服务商可以代小程序发起备案申请。在申请小程序备案之前&#xff0c;需要确保小程序基本信息已填写完成、小程序至少存在一个…

硬件笔记(26)---- 高速电路中滤波电容的选取

先要知道电容的等效电路 其中ESL取决于电容的类型和封装&#xff0c;一般用贴片陶瓷电容为例&#xff0c;对于直插式电解电容&#xff0c;他们的ESL很大。按下表&#xff0c;封装越大&#xff0c;ESL越大&#xff0c;但是0612有些例外 0612和1206就是 长短边的区别&#xff0c;…

什么是MVC三层结构

1.MVC&#xff08;三层结构&#xff09; MVC&#xff08;Model-View-Controller&#xff09;是一种常见的软件设计模式&#xff0c;用于将应用程序的逻辑和界面分离成三个不同的组件。每个组件负责特定的任务&#xff0c;从而提高代码的可维护性和可扩展性。 以前的模式。 遇到…

【网络安全渗透】常见文件上传漏洞处理与防范

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 所属的专栏&#xff1a;网络安全渗透 景天的主页&#xff1a;景天科技苑 文章目录 1.文件上传漏洞1.1. 描述1.2. 危害1.3. 有关文件上传的知识1.4…

【兔子机器人】修改GO、车轮电机ID(软件方法、硬件方法)以及修正VMC腿部初始化夹角

一、GO电机修改ID 1、硬件方法 利用上位机直接修改GO电机的id号&#xff1a; 打开调试助手&#xff0c;点击“调试”&#xff0c;查询电机&#xff0c;修改id号&#xff0c;即可。 但先将四个GO电机连接线拔掉&#xff0c;不然会将连接的电机一并修改。 利用24V电源给GO电机…

Java_12 杨辉三角 II

杨辉三角 II 给定一个非负索引 rowIndex&#xff0c;返回「杨辉三角」的第 rowIndex 行。 在「杨辉三角」中&#xff0c;每个数是它左上方和右上方的数的和。 示例 1: 输入: rowIndex 3 输出: [1,3,3,1] 示例 2: 输入: rowIndex 0 输出: [1] 示例 3: 输入: rowIndex 1 输…

【应急响应靶场web2】

文章目录 前言 一、应急响应 1、背景 2、webshell查杀 3、日志排查 1&#xff09;apache日志 2&#xff09;nginx日志 3&#xff09;ftp日志 4、隐藏账户 5、文件筛选 二、漏洞复现 总结 前言 靶场来源&#xff1a;知攻善防实验室 一、应急响应 1、背景 小李在某…

VMware 配置虚拟机网络

之前需要完成的任务 &#xff08;1&#xff09;、下载和安装VMware-Workstation-Pro.exe软件&#xff0c;推荐16.0版本 &#xff08;2&#xff09;、下载centOS7镜像&#xff0c;可以在阿里云下载。 &#xff08;3&#xff09;、VM创建一个虚拟机&#xff0c;并且使用本地已下载…