C语言学习之动态内存的管理

        学完前面的C语言内容后,我们之前给内存开辟空间的方式是这样的。

int val=20;
char arr[10]={0};

         我们发现这个方式有两个弊端:空间是固定的;同时在声明的时候必须指定数组的长度,一旦确定了大小就不能调整的。

        而实际应用的过程中,我们发现定长的数组往往是不能满足需要的。因此我们需要对内存进行动态化的处理。

目录

malloc函数

free函数

calloc函数

realloc函数

动态内存管理的几个常见错误

对空指针解引用

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

对非动态内存使用free函数

 使用free函数释放了一部分

同一动态内存多次释放

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

一些经典的内存方面的例题:

1.

2.

3.

4.

柔性数组

柔性数组的特点

        柔性数组的使用

        柔性数组的优势

C/C++中内存区域划分


内存三大区域主要存储的数据类型。

malloc函数

        malloc是C语言动态内存开辟的一个函数,它的语法形式是这样的

void * malloc(size_t size)

         其中size是指定的大小(字节)

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

        如果开辟成功则返回一个指向开辟好空间的指针则返回一个指向开辟好空间的指针。;如果开辟失败则返回一个NULL指针,因此一定要对malloc返回值做检查。

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

        如果参数size的数值为0,则malloc的行为标准是未定义,具体行为取决于编译器。

        使用该函数前需要包含头文件<stdlib.h>

        但是当我们申请空间调用后一定要销毁内存空间,因此我们还需要free函数

free函数

        free函数专门用来做动态内存的释放和回收的函数。语法结构如下:

void *free(void *ptr)

        ptr中存放的是要释放的空间的起始位置。

        如果ptr指向的内存空间不是动态的,free行为未定义;如果ptr指向的内存是NULL指针,则函数什么都不做。

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
int main()
{int *p=(int*)malloc(sizeof(int)*10);if (p == NULL){perror("空间申请失败");return 1;//异常返回,退出程序}//使用内存for (int i = 0; i < 10; i++){p[i] = i;}for (int i = 0; i < 10; i++){printf("%d ", p[i]);}//释放内存free(p);//如果这里不写free函数则程序运行时候系统自动回收这些内存。//但是可能导致内存泄漏//同时这么写是很危险的,因为p被释放时候就是野指针了。后续如果接着调用p,可能会导致程序崩溃//所以要在使用完内存之后立即将p置为NULLp = NULL;return 0;
}

calloc函数

        calloc函数也可以用来动态内存分配,语法结构如下:

void*calloc(size_t num,size_t size)

        功能是给num个size元素开辟一块空间,并将其初始化为零。

        看着与malloc的功能相似,区别是calloc在返回地址之前吧申请的空间每个字节全部初始化为0。

        malloc效率更高一点,calloc不需要初始化。

realloc函数

        realloc函数让动态内存更加灵活的调整。

        如果发现申请空间过小或者过大的时候,为了合理使用内存,灵活的调整内存的大小,而realloc函数就是为了这个而生的。

        它的语法结构如下:

void*realloc(void *ptr,size_t size)

        ptr是要调整的内存的起始位置,size是调整后新的内存大小(单位为字节)

        返回值为调整后内存起始位置。

        这个函数调整原有内存的大小基础上会将原数据迁移到新空间。

 使用realloc几种情况

1.后面有足够大的空间,直接扩容。

2.后面空间足够但是被占用了,所以在新空间找一块足够大满足条件的内存空间,将旧空间数据拷贝到新的空间,随后释放掉旧空间并返回新空间的地址

动态内存管理的几个常见错误

对空指针解引用

        

#include<stdlib.h>
int main()
{int* p = malloc(sizeof(int) * 10);int i = 0;for (i = 0; i < 10; i++){*(p + i) = i=1; //可能产生空指针解引用操作}return 0;
}

所以要判断malloc返回值

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

        之前我们知道数组是不能越界访问的。动态内存也是如此,申请的时候也是有大小的,必须要在自己的范围内使用,超出范围就是非法访问。
        错误写法

#include<stdio.h>
#include<stdlib.h>
int main()
{int* p = malloc(sizeof(int) * 10);int i = 0;for (i = 0; i <= 10; i++){*(p + i) = i=1; //当i为10的时候形成越界访问了}return 0;
}

对非动态内存使用free函数

错误写法

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

 使用free函数释放了一部分

错误写法:

#include<stdio.h>
#include<stdlib.h>
int main()
{int* p = (int*)malloc(sizeof(int)*10);if (p == NULL){printf("内存分配失败!\n");return -1;}int i = 0;for (i = 0; i < 5; i++){*p = 5;p++;}free(p);//p指向的不再是动态开辟的空间的起始地址。p = NULL;return 0;
}

同一动态内存多次释放

               错误写法:

#include<stdio.h>
#include<stdlib.h>
void test()
{int* p=malloc(sizeof(int)*10);if (p == NULL){printf("内存分配失败!\n");return 1;}free(p);free(p);//释放两次,第二次释放会导致程序崩溃。
}
int main()
{test();return 0;
}

      可以这样改正

#include<stdio.h>
#include<stdlib.h>
void test()
{int* p=malloc(sizeof(int)*10);if (p == NULL){printf("内存分配失败!\n");return 1;}free(p);p = NULL;free(p);//释放两次,第二次释放会导致程序崩溃。
}
int main()
{test();return 0;
}

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

        错误写法:

#include<stdio.h>
#include<stdlib.h>
void test()
{int* p=malloc(sizeof(int)*10);if (p != NULL){*p = 10;}
}
int main()
{test();while (1);//无法知道前面申请10个字节的地址return 0;
}

正确写法:要在函数之内释放内存

        或者也可以这样

#include<stdio.h>
#include<stdlib.h>
int test()
{int* p=malloc(sizeof(int)*10);if (p != NULL){*p = 10;}return p;
}
int main()
{int *pr=test();//使用*pr = 100;free(pr);//释放pr = NULL;while (1);//无法知道前面申请10个字节的地址return 0;
}

        只要保证一个原则:malloc、calloc、realloc必须要和free函数成对出现。

        realloc函数也能实现malloc函数的效果

        但是即使你成对存在,也可能内存泄漏

        如下图所示,在test函数中,在释放内存之前就已经返回了,所以内存没有释放,因此内存泄漏。

#include<stdio.h>
#include<stdlib.h>
int test()
{int* p=malloc(sizeof(int)*10);if (p != NULL){*p = 10;}int n = 20;if (n > 10){//代码}return p;free(p);p = NULL;
}
int main()
{int *pr=test();//使用*pr = 100;free(pr);//释放pr = NULL;while (1);//无法知道前面申请10个字节的地址return 0;
}

一些经典的内存方面的例题:

1.

void GetMemory(char *p)
{p = (char*)malloc(100);
}
void test()
{char* str = NULL;GetMemory(str);strcpy(str, "Hello World!");printf("%s\n", str);
}

运行test()函数后的结果:

运行崩溃。

解析:这里面,test函数中GetMemory函数的调用是直接将指针变量str本身传递过去了,是传值调用,str的值没有变化,仍然是NULL,所以在下一步进入strcpy函数,在strcpy函数中会对NULL进行解引用,造成了非法访问,程序就会崩溃。

可以这么更改:(这种方法更好一点)

void GetMemory(char **p)
{*p = (char*)malloc(100);
}
void test()
{char* str = NULL;GetMemory(&str);strcpy(str, "Hello World!");printf("%s\n", str);//printf(str)也可以free(str);str=NULL;
}

也可以这样改 :

char* GetMemory(char **p)
{*p = (char*)malloc(100);return p;
}
void test()
{char* str = NULL;str=GetMemory(&str);strcpy(str, "Hello World!");printf("%s\n", str);//printf(str)也可以free(str);str=NULL;
}

2.

char *GetMemory()
{char p[] = "Hello World!";return p;
}
void test()
{char* str = NULL;str=GetMemory(str);printf(str);
}

 运行test函数的后果:

运行结果错误。

解析:p的地址可以正常传递给str,但是p数组是函数的局部变量,出了函数就会被回收,p数组的内收可能就被改了。这个就是返回栈空间地址的问题。

栈区上空间要么free函数回收,要么程序结束回收。

可以这样改:

char *GetMemory()
{static char p[] = "Hello World!";return p;
}
void test()
{char* str = NULL;str=GetMemory(str);printf(str);
}

3.

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

求test函数的运行结果

 程序崩溃

解析:内存没有释放。

4.

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

求运行test函数的结果:

运行错误。

解析:str在free函数之后没有置为NULL。

这些题目出自于《高质量C/C++编程》

柔性数组

        柔性数组在结构体中,且最后一个成员是未知大小的数组,这个数组就是柔性数组。

struct S
{int a;int S[];//未指明大小,就是柔性数组
};

        有些编译器可能不支持这种写法,可以改成

struct S
{int a;int S[0];//未指明大小,就是柔性数组
};

柔性数组的特点

        结构体中柔性数组前至少要有一个成员

        sizeof返回这种结构大小不包括柔性数组

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

#include<stdio.h>
typedef struct st_type
{int a;char c;int S[0];//未指明大小,就是柔性数组
}st;
int main()
{printf("%zd\n", sizeof(st));return 0;
}

结果为5。

        柔性数组的使用

        

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
typedef struct st_type
{int a;char c;int S[0];//未指明大小,就是柔性数组
}st;
int main()
{st*p=(st*)malloc(sizeof(st)+10*sizeof(int));//分配10个int的空间if (p == NULL){perror("malloc error");return -1;}//使用内存p->a = 10;p->c = 0;for (int i = 0; i < 5; i++){p->S[i] = i+1;}//空间不够// 扩容st*q=(st*)realloc(p, sizeof(st) + 40 * sizeof(int));if (q != NULL){p = q;q = NULL;}//释放内存free(q);q = NULL;return 0;
}

应用二:相当于获得了10个整型元素空间

#include<stdio.h>
#include<stdlib.h>
typedef struct st_type
{int i;int S[];//未指明大小,就是柔性数组
}st;
int main()
{int i = 0;st*p=(st*)malloc(sizeof(st)+10*sizeof(int));//分配10个int的空间p->i = 100;for (i = 0; i < 5; i++){p->S[i] = i+1;}free(p);p = NULL;return 0;
}

        柔性数组的优势

        上图代码也可以这样写:

#include<stdio.h>
#include<stdlib.h>
typedef struct st_type
{int i;int *p_a;
}st;
int main()
{int i = 0;st*p=(st*)malloc(sizeof(st)+10*sizeof(int));//分配10个int的空间p->i = 100;p->p_a = (int*)malloc(p->i*sizeof(int));for (i = 0; i < 5; i++){p->p_a[i] = i+1;}free(p->p_a);p->p_a = NULL;free(p);p = NULL;return 0;
}

        二者 均可,但是方法一有两大好处:

1.方便内存释放

2.有利于访问速度

C/C++中内存区域划分

C/C++中内存划分的几个区域

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

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

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

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

具体可以参考如下:

        

感谢看到这里的读者大大们,求一个赞,谢谢

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

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

相关文章

【深度学习-Day 2】图解线性代数:从标量到张量,理解深度学习的数据表示与运算

Langchain系列文章目录 01-玩转LangChain&#xff1a;从模型调用到Prompt模板与输出解析的完整指南 02-玩转 LangChain Memory 模块&#xff1a;四种记忆类型详解及应用场景全覆盖 03-全面掌握 LangChain&#xff1a;从核心链条构建到动态任务分配的实战指南 04-玩转 LangChai…

首页数据展示

排版 现在做首页的排版&#xff0c;依旧是偷antd里面的东西 使用card包裹list的样式 import React from react import axios import { Card, Col, Row, List } from antd import { EditOutlined, EllipsisOutlined, SettingOutlined } from ant-design/icons; import { Avat…

使用Set和Map解题思路

前言 Set和Map这两种数据结构,在解决一些题上&#xff0c;效率很高。跟大家简单分享一些题以及如何使用Set和Map去解决这些题目。 题目链接 136. 只出现一次的数字 - 力扣&#xff08;LeetCode&#xff09; 138. 随机链表的复制 - 力扣&#xff08;LeetCode&#xff09; 旧…

尝试leaflet+webassemly

前言 笔者在github发现rust版本的leaflet&#xff0c;发现是用wasm-bindgen包装的&#xff0c;尝试使用一下 Issues slowtec/leaflet-rshttps://github.com/slowtec/leaflet-rs 正文 准备 新建一个react项目&#xff0c;安装rsw依赖 pnpm i -D vite-plugin-rsw cargo ins…

机器学习实战,天猫双十一销量与中国人寿保费预测,使用多项式回归,梯度下降,EDA数据探索,弹性网络等技术

前言 很多同学学机器学习时总感觉&#xff1a;“公式推导我会&#xff0c;代码也能看懂&#xff0c;但自己从头做项目就懵”。 这次我们选了两个小数据集&#xff0c;降低复杂度&#xff0c;带大家从头开始进行分析&#xff0c;建模&#xff0c;预测&#xff0c;可视化等&…

SQL数据库系统全解析:从入门到实践

一、数据库世界入门指南 在数字时代&#xff0c;数据就像新时代的石油&#xff0c;而数据库系统就是储存和管理这些宝贵资源的仓库。对于初学者来说&#xff0c;理解数据库的基本概念是迈入这个领域的第一步。 数据库本质上是一个有组织的数据集合&#xff0c;它允许我们高效…

【大模型】图像生成:StyleGAN3:生成对抗网络的革命性进化

深度解析StyleGAN3&#xff1a;生成对抗网络的革命性进化 技术演进与架构创新代际技术对比StyleGAN3架构解析 环境配置与快速入门硬件要求安装步骤预训练模型下载 实战全流程解析1. 图像生成示例2. 自定义数据集训练3. 潜在空间操作 核心技术深度解析1. 连续信号建模2. 傅里叶特…

PHP-Cookie

Cookie 是什么&#xff1f; cookie 常用于识别用户。cookie 是一种服务器留在用户计算机上的小文件。每当同一台计算机通过浏览器请求页面时&#xff0c;这台计算机将会发送 cookie。通过 PHP&#xff0c;您能够创建并取回 cookie 的值。 设置Cookie 在PHP中&#xff0c;你可…

“Everything“工具 是 Windows 上文件名搜索引擎神奇

01 Everything 和其他搜索引擎有何不同 轻量安装文件。 干净简洁的用户界面。 快速文件索引。 快速搜索。 快速启动。 最小资源使用。 轻量数据库。 实时更新。 官网&#xff1a;https://www.voidtools.com/zh-cn/downloads/ 通过网盘分享的文件&#xff1a;Every…

CSS:选择器-基本选择器

文章目录 1、通配选择器2、元素选择器3、类选择器4、ID选择器 1、通配选择器 2、元素选择器 3、类选择器 4、ID选择器

一种动态分配内存错误的解决办法

1、项目背景 一款2年前开发的无线网络通信软件在最近的使用过程中出现网络中传感器离线的问题&#xff0c;此软件之前已经使用的几年了&#xff0c;基本功能还算稳定。这次为什么出了问题。 先派工程师去现场调试一下&#xff0c;初步的结果是网络信号弱&#xff0c;并且有个别…

React 第三十四节 Router 开发中 useLocation Hook 的用法以及案例详解

一、useLocation基础用法 作用&#xff1a;获取当前路由的 location 对象 返回对象结构&#xff1a; {pathname: "/about", // 当前路径search: "?namejohn", // 查询参数&#xff08;URL参数&#xff09;hash: "#contact", …

DeepSeek-Prover-V2-671B最新体验地址:Prover版仅适合解决专业数学证明问题

DeepSeek-Prover-V2-671B最新体验地址&#xff1a;Prover版仅适合解决专业数学证明问题 DeepSeek 团队于 2025 年 4 月 30 日正式在Hugging Face开源了其重量级新作 —— DeepSeek-Prover-V2-671B&#xff0c;这是一款专为解决数学定理证明和形式化推理任务而设计的超大规模语…

tornado_登录页面(案例)

目录 1.基础知识​编辑 2.脚手架&#xff08;模版&#xff09; 3.登录流程图&#xff08;processon&#xff09; 4.登录表单 4.1后&#xff08;返回值&#xff09;任何值&#xff1a;username/password &#xff08;4.1.1&#xff09;app.py &#xff08;4.1.2&#xff…

Android学习总结之自定义view设计模式理解

面试题 1&#xff1a;请举例说明自定义 View 中模板方法模式的应用 考点分析 此问题主要考查对模板方法模式的理解&#xff0c;以及该模式在 Android 自定义 View 生命周期方法里的实际运用。 回答内容 模板方法模式定义了一个操作的算法骨架&#xff0c;把一些步骤的实现延…

【Scrapy】简单项目实战--爬取dangdang图书信息

目录 一、基本步骤 1、新建项目 &#xff1a;新建一个新的爬虫项目 2、明确目标 &#xff08;items.py&#xff09;&#xff1a;明确你想要抓取的目标 3、制作爬虫 &#xff08;spiders/xxspider.py&#xff09;&#xff1a;制作爬虫开始爬取网页 4、存储内容 &#xff08;p…

开源CMS系统的SEO优化功能主要依赖哪些插件?

在当今互联网时代&#xff0c;搜索引擎优化&#xff08;SEO&#xff09;是网站获取流量的核心手段之一。开源内容管理系统&#xff08;CMS&#xff09;因其灵活性和丰富的插件生态&#xff0c;成为许多开发者和企业的首选。本文将以主流开源CMS为例&#xff0c;深入解析其SEO优…

在 JMeter 中使用 BeanShell 获取 HTTP 请求体中的 JSON 数据

在 JMeter 中&#xff0c;您可以使用 BeanShell 处理器来获取 HTTP 请求体中的 JSON 数据。以下是几种方法&#xff1a; 方法一&#xff1a;使用前置处理器获取请求体 如果您需要在发送请求前访问请求体&#xff1a; 添加一个 BeanShell PreProcessor 到您的 HTTP 请求采样器…

在 WSL (Windows Subsystem for Linux) 中配置和安装 Linux 环境

在 WSL (Windows Subsystem for Linux) 中配置和安装 Linux 环境 WSL 允许你在 Windows 上运行 Linux 环境&#xff0c;以下是详细的配置和安装指南。 1. 安装前的准备工作 系统要求 Windows 10 版本 2004 及更高版本(内部版本 19041 及更高版本)或 Windows 11 64 位系统 虚…

AlphaFold蛋白质结构数据库介绍

AlphaFold Protein Structure Database (AlphaFold DB) 是 DeepMind + EMBL-EBI 合作开发的公开蛋白质结构预测数据库,是利用 AlphaFold2/AlphaFold3 AI模型 预测的全基因组级蛋白质三维结构库。 网址: https://alphafold.ebi.ac.uk 项目内容主办单位DeepMind + EMBL-EBI上线…