『C语言进阶』动态内存管理

在这里插入图片描述
🔥博客主页 小羊失眠啦.
🔖系列专栏 C语言LinuxCpolar
❤️感谢大家点赞👍收藏⭐评论✍️


在这里插入图片描述

文章目录

  • 前言
  • 一、动态内存函数的介绍
    • 1.1 malloc和free函数
    • 1.2 calloc函数
    • 1.3 realloc函数
  • 二、常见的动态内存错误
    • 2.1 对NULL指针的解引用
    • 2.2 对动态开辟空间的越界访问
    • 2.3 对非动态开辟内存使用free释放
    • 2.4 使用free释放一块动态开辟内存的一部分
    • 2.5 对同一块动态内存多次释放
    • 2.6 动态开辟内存忘记释放(内存泄漏)
  • 三、经典例题分析
    • 3.1 题目1
    • 3.2 题目2
    • 3.3 题目3
    • 3.4 题目4
  • 四、C/C++程序的内存开辟
  • 五、柔性数组
    • 5.1 什么是柔性数组
    • 5.2 柔性数组的特点
    • 5.3 柔性数组的使用
    • 5.4 柔性数组的优点

前言

看到今天的主题动态内存管理,相信很多小伙伴心中有这样一个疑惑:为什么存在动态内存分配?那是因为现在我们掌握的内存开辟方式开辟的空间都是固定的,但是对于空间的需求,有时候我们需要的空间大小在程序运行的时候才能知道, 那数组的编译时开辟空间的方式就不能满足了。 这时候就要使用动态内存开辟了。


一、动态内存函数的介绍

1.1 malloc和free函数

在这里插入图片描述

函数功能:

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

头文件:

#include<stdlib.h>

malloc函数的应用:

//开辟一个有10个元素的数组
int* p = (int*)malloc(40);
if (p == NULL)
{perror("malloc");return 1;
}

注意:

  • 如果开辟成功,则返回一个指向这块空间的指针(起始地址),不会初始化空间的内容

  • 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查

  • 返回值的类型是void * ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定

  • 如果参数size为0,malloc的行为是标准未定义的,取决于编译器

在这里插入图片描述

函数功能:

专门用来做动态内存的释放和回收

头文件:

#include<stdlib.h>

free函数的应用:

#include<stdio.h>
#include<stdlib.h>int main()
{//开辟一个有10个元素的数组int* p = (int*)malloc(40);if (p == NULL){perror("malloc");return 1;}free(p);p = NULL;return 0;
}

注意:

  • free只能释放动态开辟的内存,如果参数ptr指向的空间不是动态开辟的,那free函数的行为是未定义的
  • 如果参数ptr是NULL指针,则函数什么事都不做
  • 使用free函数释放空间后,p指向这块空间没用了,就变成野指针,所以我们要在释放空间后,将p置为空指针
int a = 10;
int* p = &a;
free(p);     //error
p = NULL;

1.2 calloc函数

在这里插入图片描述

函数功能:

为num个大小为size的元素开辟一块空间,并且把空间的每个字节初始化为0

头文件:

#include<stdlib.h>

calloc函数的应用:

#include<stdio.h>
#include<stdlib.h>
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++){printf("%d ", p[i]);}free(p);p = NULL;return 0;
}

运行结果:

0 0 0 0 0 0 0 0 0 0

注意:

calloc函数与malloc函数的区别只在于calloc会在返回地址之前把申请的空间的每个字节都初始化为0

1.3 realloc函数

在这里插入图片描述

函数功能:

  1. realloc函数让动态内存管理更加灵活
  2. 有时我们发现过去申请的空间太小了,有时我们又会觉得申请的空间过大了,那为了合理的内存,我们会对内存大小做灵活的调整,那realloc就可以做到对动态内存大小的调整

头文件:

#include<stdlib.h>
  1. ptr是要调整的内存地址
  2. size调整之后新的大小
  3. 返回值为调整之后的内存起始地址
  4. 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间

realloc函数的应用:

#include<stdio.h>
#include<stdlib.h>
int main()
{int* p = (int*)malloc(40);if (p == NULL){perror("malloc");return 1;}int i = 0;for (i = 0; i < 10; i++){p[i] = i + 1;}int* ptr = (int*)realloc(p, 80);if (ptr != NULL){p = ptr;}else{perror("realloc");}//打印数据for (i = 0; i < 20; i++){printf("%d ", p[i]);}free(p);p = NULL;return 0;
}

运行结果:

1 2 3 4 5 6 7 8 9 10 -842150451 -842150451 -842150451 -842150451 -842150451 -842150451 -842150451 -842150451 -842150451 -842150451

这个函数也会出现增容失败的时候(返回空指针,意思空间被释放,会导致原来的空间也被释放)所以不能把返回的指针直接放在原来的指针变量里,应该再定义一个指针变量存放返回地址,先判断是否空指针(增容失败),不是空指针再赋值给p

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

  • 原有的空间后面的空间足够,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化
  • 原有的空间后面的空间不够,在堆空间另找一个合适大小的连续空间来使用,把旧的数据拷贝到新的空间中,会将旧空间释放掉,返回新的地址

二、常见的动态内存错误

2.1 对NULL指针的解引用

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

如果空间开辟失败返回空指针,我们对空指针解引用,程序就会出现错误,所以我们要判断返回的是否为空指针

代码修改:

int* p = (int*)malloc(40);if (p == NULL){perror("malloc");return 1;}*p=20;free(p);

2.2 对动态开辟空间的越界访问

#include<stdio.h>
int main()
{int* p = (int*)malloc(10 * sizeof(int));if (p == NULL){perror("malloc");return 1;}for (int i = 0; i <= 10; i++){*(p + i) = i;//当i是10的时候越界访问}free(p);p = NULL;return 0;
}

只有10个整型的空间,却访问了11个整型的空间,越界访问

2.3 对非动态开辟内存使用free释放

int a = 10;
int* p = &a;
free(p);     //error:非动态开辟,不可以释放
p = NULL;

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

int main()
{int* p = (int*)malloc(40);if (p == NULL){perror("malloc");return 1;}p++;free(p);//p不再指向动态内存的起始位置,不可以释放,必须从起始位置释放p = NULL;return 0;
}

2.5 对同一块动态内存多次释放

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

不可以重复释放(当把p赋值为空指针后再释放是可以的)

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

void test()
{int* p = (int*)malloc(100);if (NULL != p){*p = 20;}//记得要释放
}int main()
{test();//在这里释放不可以return 0;//或者把函数test的返回值改为int* ,在主函数接受一下,这样就可以在主函数释放
}

忘记释放不再使用的动态开辟的空间会造成内存泄漏

动态申请的内存空间不会因为出了作用域自动销毁

两种销毁方式:

  1. free
  2. 程序结束

三、经典例题分析

3.1 题目1

void GetMemory(char* p)
{p = (char*)malloc(100);//没有释放//并没有返回任何值,也没有改变str的值
}void Test(void)
{char* str = NULL;GetMemory(str);strcpy(str, "hello world");//常量字符串传递的是h的地址,所以是正确的//str是空指针,在这里是非法访问,程序会崩溃printf(str);//写法正确,但这一步之前程序已经崩溃
}int main()
{Test();return 0;
}

运行结果:程序崩溃

1.对NULL指针进行解引用操作,程序会崩溃

2.没有释放空间,存在内存泄漏问题

3.2 题目2

char* GetMemory(void)
{char p[] = "hello world";return p;
}
//出作用域后p销毁,开辟的空间还给操作系统void Test(void)
{char* str = NULL;str = GetMemory();//str变成野指针,再去访问这块空间就是非法访问,结果是随机值printf(str);
}int main()
{Test();return 0;
}

可以用static修饰变量p,使p的声明周期变长

3.3 题目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);
}int main()
{Test();return 0;
}

3.4 题目4

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;
}

四、C/C++程序的内存开辟

在这里插入图片描述

  1. 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。

  2. 堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分配方式类似于链表。

  3. 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。

  4. 码段:存放函数体(类成员函数和全局函数)的二进制代码。

实际上普通的局部变量实在栈区分配空间的,栈区的特点是在上面创建的变量出了作用域就销毁,但是被static修饰的变量存在数据段(静态区),数据段的特点使在上面创建的变量,直到程序结束才销毁,所以生命周期长


五、柔性数组

5.1 什么是柔性数组

C99中,结构体中的最后一个元素允许是未知大小的数组,这就加柔性数组成员

写法一:

struct s1
{int n;int arr[0];//大小是未指定的,并不是0
};

写法二:

struct s1
{int n;int arr[];
};

5.2 柔性数组的特点

  • 结构中的柔性数组成员前面必须至少一个其他成员。
  • sizeof 返回的这种结构大小不包括柔性数组的内存。
  • 包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
struct S
{int i;int a[0];
};int main()
{printf("%d\n",sizeof(S));return 0;
}

运行结果:

4

柔性数组大小未知,无法计算大小

5.3 柔性数组的使用

我们开辟好空间后,发现空间不够用,可以使用realloc增容

写法一:

struct S
{int n;int arr[];
};int main()
{struct S* p = (struct S*)malloc(sizeof(struct S) + 40);if (p == NULL){perror("malloc");return 1;}p->n = 100;int i = 0;for (i = 0; i < 10; i++){p->arr[i] = i + 1;}//释放free(p);p = NULL;
}

写法二:

struct S
{int n;int* arr;
};int main()
{struct S* p = (struct S*)malloc(sizeof(struct S));if (p == NULL){perror("malloc");return 1;}p->n = 100;p->arr = (int*)malloc(40);if (p->arr == NULL){perror("malloc");return 1;}int i = 0;for (i = 0; i < 10; i++){p->arr[i] = i + 1;}//增容int* ptr = (int*)realloc(p->arr,   60);if (ptr == NULL){perror("realloc");return 1;}p->arr = ptr;for (i = 0; i < 15; i++){printf("%d ", p->arr[i]);}//释放free(p->arr);p->arr = NULL;free(p);p = NULL;return 0;
}

要先释放里面的,再释放外面的。如果先把p释放了,就找不到arr的地址了。

总结:

代码一相对于来说比较好:(1)方便内存释放 如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把整个结构体返回给 用户。用户调用free可以释放结构体,但是用户并不知道这个结构体内的成员也需要free,所以你不能指望用户来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存一次性分配好了,并返回给用户一个结构体指针,用户做一次free就可以把所有的内存也给释放掉。 (2)这样有利于访问速度. 连续的内存有益于提高访问速度,也有益于减少内存碎片。

(代码二:开辟和释放的次数多,容易出错;容易形成内存碎片)

5.4 柔性数组的优点

我们上面的代码二,可以完美的替代柔性数组的功能,却为什么还是创造除了柔性数组呢。在这就要说到柔性数组的优点:

1.内存方便释放

在柔性数组中,我们只使用了一次malloc,方法二中使用了两次malloc,容易造成忘记释放,没有都释放或释放顺序错误的问题。

2.访问速度快,节省空间

多次开辟空间,内存与内存之间的内存碎片会很多,不内存的利用率就会变低。同时,连续的内存有益于提高访问速度。


在这里插入图片描述

本次的内容到这里就结束啦。希望大家阅读完可以有所收获,同时也感谢各位铁汁们的支持。文章有任何问题可以在评论区留言,小羊一定认真修改,写出更好的文章~~

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

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

相关文章

行业模型应该如何去拆解?

行业模型应该如何去拆解&#xff1f; 拆解行业模型是一个复杂的过程&#xff0c;涉及对整个行业的深入分析和理解。下面是一些步骤和方法&#xff0c;可以帮助你系统地拆解行业模型&#xff1a; 1. 确定行业范围 定义行业&#xff1a;明确你要分析的行业是什么&#xff0c;包括…

React中的Virtual DOM(看这一篇就够了)

文章目录 前言了解Virtual DOMreact创建虚拟dom的方式React Element虚拟dom的流程虚拟dom和真实dom的对比后言 前言 hello world欢迎来到前端的新世界 &#x1f61c;当前文章系列专栏&#xff1a;react合集 &#x1f431;‍&#x1f453;博主在前端领域还有很多知识和技术需要掌…

在pycharm中创建python模板文件

File——>Setting——>File and Code Templates——>Python Scripts 在文本框中输入模板内容

vue首页多模块布局(标题布局)

<template><div class"box"><div class"content"><div class"box1" style"background-color: rgb(245,23,156)">第一个</div><div class"box2" style"background-color: rgb(12,233,…

windows下使用FFmpeg开源库进行视频编解码完整步聚

最终解码效果: 1.UI设计 2.在控件属性窗口中输入默认值 3.复制已编译FFmpeg库到工程同级目录下 4.在工程引用FFmpeg库及头文件 5.链接指定FFmpeg库 6.使用FFmpeg库 引用头文件 extern "C" { #include "libswscale/swscale.h" #include "libavdevic…

composer安装thinkphp6报错

composer安装thinkphp6报错&#xff0c; 查看是否安装了对应的PHP扩展&#xff0c;我这边使用的是宝塔的环境&#xff0c;全程可以可视化操作 这样就可以安装完成了

【AIGC】百度文库文档助手之 - 一键生成PPT

百度文库文档助手之 - 一键生成PPT 引言一、文档助手&#xff1a;体验一键生成PPT二、文档助手&#xff1a;进阶用法三、其它生成PPT的方法3.1 ChatGPT3.2 文心一言 引言 就在上个月百度文库升级为一站式智能文档平台&#xff0c;开放四大AI能力&#xff1a;智能PPT、智能总结、…

【Ansible自动化运维工具 第一部分】Ansible常用模块详解(附各模块应用实例和Ansible环境安装部署)

Ansible常用模块 一、Ansible1.1 简介1.2 工作原理1.3 Ansible的特性1.3.1 特性一&#xff1a;Agentless&#xff0c;即无Agent的存在1.3.2 特性二&#xff1a;幂等性 1.4 Ansible的基本组件 二、Ansible环境安装部署2.1 安装ansible2.2 查看基本信息2.3 配置远程主机清单 三、…

计算机中了mallox勒索病毒怎么办,勒索病毒解密,数据恢复

最近一段时间&#xff0c;云天数据恢复中心陆续收到很多企业的求助&#xff0c;企业的计算机服务器遭到了mallox勒索病毒攻击&#xff0c;导致企业的数据库无法正常使用&#xff0c;严重影响了企业的正常生产生活&#xff0c;为此&#xff0c;云天数据恢复中心的工程师通过对此…

【Java笔记+踩坑】设计模式——原型模式

导航&#xff1a; 【Java笔记踩坑汇总】Java基础JavaWebSSMSpringBootSpringCloud瑞吉外卖/黑马旅游/谷粒商城/学成在线设计模式面试题汇总性能调优/架构设计源码-CSDN博客​ 目录 零、经典的克隆羊问题&#xff08;复制10只属性相同的羊&#xff09; 一、传统方案&#xff1…

酒类商城小程序怎么做

随着互联网的快速发展&#xff0c;线上购物越来越普及。酒类商品也慢慢转向线上销售&#xff0c;如何搭建一个属于自己的酒类小程序商城呢&#xff1f;下面就让我们一起来看看吧&#xff01; 一、登录乔拓云平台 首先&#xff0c;我们需要进入乔拓云平台的后台&#xff0c;点击…

使用boost.mysql来操作mysql 数据库

准备条件 1. visual studio 2019 2. boost库 3. 安装本地的mysql 服务器&#xff0c;boost.mysql对mysql有版本要求最好8.0&#xff0c;具体参考官方文档 安装 使用Nuget安装boost 要安装 openssl&#xff0c;否则的话编译其他项目会产生依赖ssl的错误 安装mysql 省略 …

Java操作Excel

一、Java操作Excel 二、Excel根据单元格状态自动变更行背景颜色 用excel记录和跟进工作的时候&#xff0c;设置背景颜色&#xff0c;以此让记录更加突出&#xff0c;方便查看&#xff0c;但是手动修改背景颜色一来麻烦容易漏&#xff0c;也可能颜色不统一&#xff0c;我们可以试…

PHP 数据库交互优化,根据传参查询

接上文 修改以下内容 将查询的 uid 改为 username&#xff0c;同时在 user 和 message 两张表中查询 $sql "select m.id,u.username,m.title,m.content from user u,message m where u.idm.uid;"根据 message 中的 id 查询&#xff0c;形式为 http://127.0.0.1/m…

数学与经济管理

数学与经济管理&#xff08;2-4分&#xff09; 章节概述 最小生成树问题 答案&#xff1a;23 讲解地址&#xff1a;74-最小生成树问题_哔哩哔哩_bilibili 最短路径问题 答案&#xff1a;81 讲解地址&#xff1a;75-最短路径问题_哔哩哔哩_bilibili 网络与最大流量问题 真题 讲解…

【STL】:vector用法详解

朋友们、伙计们&#xff0c;我们又见面了&#xff0c;本期来给大家解读一下有关vector的基础用法&#xff0c;如果看完之后对你有一定的启发&#xff0c;那么请留下你的三连&#xff0c;祝大家心想事成&#xff01; C 语 言 专 栏&#xff1a;C语言&#xff1a;从入门到精通 数…

k8s-----24、亲和力Affinity

1、应用场景 pod和节点间的关系&#xff1a; 某些Pod优先选择有ssdtrue标签的节点&#xff0c;如果没有在考虑部署到其它节点;某些Pod需要部署在ssdtrue和typephysical的节点上&#xff0c;但是优先部署在ssdtrue的节点上; pod和pod间的关系&#xff1a; 同一个应用的Pod不…

Wt库的C++下载器程序

以下是一个使用Wt库的C下载器程序&#xff0c;用于下载音频文件。此程序使用了的代码。 #include <Wt/Wt.h> #include <Wt/Http/DiskCache.h> #include <Wt/Http/HttpClient.h> ​ // 定义一个函数来获取服务器 static std::string get_proxy() {// 使用Wt:…

idea免费插件分享

分享一些在开发中常用到的idea插件&#xff0c;都是一些我自己常用的&#xff0c;希望对各位程序员有帮助吧。 1、Chinese Language 汉化插件&#xff1a;中文语言包将为您的 IntelliJ IDEA, AppCode, CLion, DataGrip, GoLand, PyCharm, PhpStorm, RubyMine, WebStorm, 和Rid…

js创建 ajax 过程

目录 前言&#xff1a;AJAX 技术的重要性 详解&#xff1a;创建 AJAX 请求的步骤 1. 创建 XMLHttpRequest 对象 2. 配置请求 3. 处理响应 4. 发送请求 5. 处理异步请求 解析&#xff1a;AJAX 请求的重要性和限制 总结&#xff1a; 前言&#xff1a;AJAX 技术的重要性 …