C语言动态内存分配

        有些情况下需要开辟的空间大小在程序运行过程中才能确定下来,而常规的在栈区开辟空间是在编译时就分配好了内存,并且内存大小不能改变,因此需要引入动态内存分配,动态内存分配的内存是在堆区,需要由用户手动开辟,手动释放,并且可以由用户自行改变空间的大小。下面介绍一下动态内存分配的相关函数:

malloc和free

        malloc是c语言中的动态内存开辟函数,用于在堆区动态开辟一块连续的内存,返回指向这块内存的指针,其函数原型如下:

//size是要开辟的字节数,返回开辟空间的地址,如果开辟失败则返回NULL
//返回值是void*类型,malloc不知道开辟的空间是什么类型,需要用户自己强制类型转换成需要的类型
void* malloc(size_t size);

        free是专门用来做动态内存的释放和回收的函数,函数原型如下:

//ptr指向动态开辟的空间
void free(void* ptr);

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

        如果free的ptr指向的空间不是动态开辟的,也是标准未定义的。

        如果free的ptr是NULL,则什么都不做。

        malloc和free都声明在stdlib.h头文件中。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main()
{int num = 0;scanf("%d", &num);//键盘输入要开辟的数组大小int *ptr = (int *)malloc(num * sizeof(int));//动态内存开辟int i = 0;for (i = 0; i < num; i++)//为开辟的空间赋值{ptr[i] = i;}for (i = 0; i < num; i++)//打印{printf("%d ", ptr[i]);}free(ptr);//释放开辟的空间,防止内存泄漏ptr = NULL;//将指针置空,防止野指针return 0;
}

        free只是把ptr指向的空间还给了内存,不能再通过ptr访问原来的空间,但是ptr里面存的还是原来的地址,而这个地址里边存放的东西已经不确定了,ptr就变成了野指针,因此内存释放后需要对源地址置为空指针,防止产生野指针。

calloc

        calloc函数也用来动态分配内存,和malloc的区别是calloc会在返回地址前把申请的空间中的每个字节初始化为0,其函数原型如下:

//size是要开辟的元素的大小,num是要开辟的元素个数,如果成功则返回指向开辟空间的指针,失败返回NULL
void* calloc(size_t num, size_t size);
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main()
{int num = 0;scanf("%d", &num);//要开辟元素的个数int *ptr = (int *)calloc(num, sizeof(int));//在堆区开辟num个intint i = 0;for (i = 0; i < num; i++){printf("%d ", ptr[i]);//打印应该是全0,因为calloc会把开辟的元素初始化为0}free(ptr);ptr = NULL;return 0;
}

realloc

        realloc主要用来调整动态开辟的内存的大小,其函数原型如下:

//ptr是要改变大小的空间的地址,size是改变后的大小,如果改变成功则返回改变后的空间地址,失败则返回NULL
void* realloc(void* ptr, size_t size);

        realloc在调整空间大小时有以下两种情况:

①原有空间之后有足够大的空间供扩容:这种情况就直接在原空间后面追加空间,原空间中的数据也不会改变,返回的地址就是原空间的地址。

②原有空间之后没有足够大的空间供扩容:这种情况下会在堆空间上另找一块合适大小的连续空间作为新的空间,会将原空间的数据拷贝到新空间并释放原空间,返回的地址是新空间的地址。

int *ptr = (int*)malloc(100);//开辟100字节的堆空间
if(ptr != NULL)
{
//业务处理
}
//扩展容量
//代码1
ptr = (int*)realloc(ptr, 1000);//这样可以吗?(如果申请失败会如何?)如果拓展失败,会把原指针变成NULL,因此需要先用一个新的指针接收扩展后的地址,如果开辟成功,再赋给原变量
//代码2
int*p = NULL;
p = realloc(ptr, 1000);//用新指针接收拓展后的地址
if(p != NULL)
{ptr = p;//返回值不为空再把新指针赋给原指针
}
//业务处理
free(ptr);
ptr =NULL;
return 0;realoc(NULL,40);//等价于malloc(40)

动态内存常见错误

        在开辟和使用动态内存时可能会出现以下一些错误,需要注意并且避免:

①对NULL的解引用操作

int main()
{int* p = (int*)malloc(sizeof(INT_MAX));//动态内存开辟if(NULL == p)//所以在内存开辟后要判断开辟是否成功{return 1}*p = 10;//这里如果内存开辟失败,p就是NULL,解引用NULL会出错free(p);p = NULL;return 0;
}

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

int main()
{int* p = (int*)calloc(10,sizeof(int));if(NULL == p){return 1;}for(int i = 0; i <= 10; i++)//这里会访问到p[10],然而只开辟了十个int的空间,会越界{p[i] = i;}free(p);p = NULL;return 0;
}

③对非动态开辟的内存用free释放:

int main()
{int a = 10;int* p = &a;free(p);//这里的p指向的是栈区空间,不能用free释放,会崩溃
}

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

int main()
{    int* p = (int*)calloc(10,sizeof(int));p++;free(p);//上面改变了p指向的空间,不再指向开辟内存的起始地址,这样释放会崩溃,所以不要篡改申请的堆空间的起始地址
}

⑤对一块动态内存多次释放:

int main()
{int* p = (int*)malloc(sizeof(int));free(p);free(p);//上面已经释放了内存,p已经变成了野指针,再次释放程序会崩溃
}
int main()
{int* p = (int*)malloc(sizeof(int));free(p);p = NULL;//这里就突出了释放空间后将指针置空的重要性free(p);//上面已经释放了内存,但是p被置成了NULL,再次释放什么都不做,不过还是不建议二次释放
}

⑥动态开辟内存忘记释放:

void test()
{int* p = (int*)malloc(4);int flag = 0;scanf("%d",&flag);if(3 == flag)//如果这里输入了3,函数就会直接return,不会执行free,p申请的内存就不会释放,造成内存泄漏return;free(p);p == NULL;return;
}

        如果动态开辟的内存使用完不用free释放的话,这段空间就会内存泄漏,就相当于自己不用也不给其他人用,造成空间浪费。

经典例题

void GetMemory(char* p)
{p = (char*)malloc(100);
}
int main()
{char* str = NULL;GetMemory(str);strcpy(str,"hello world");printf(str);
}
//上述代码的问题如下
//str是实参,p是形参,是传值调用,所以改变p不会改变str,GetMemory结束后p被销毁,
//但是开辟的堆空间没释放,造成内存泄露,而str还是NULL,strcpy对NULL解引用会崩溃。
//应该按以下方法写,改成传地址,并且使用完要释放。
void GetMemory(char** p)
{*p = (char*)malloc(100);
}
int main()
{char* str = NULL;GetMemory(&str);strcpy(str,"hello world");printf(str);free(str);str = NULL
}
char* GetMemory(void)
{char p[] = "hello world";return p;
}
int main()
{char* str = NULL;str = GetMemory();printf(str);
}
//上述代码问题如下
//由于p是局部变量,p中存的是数组首地址,返回p后赋给str,
//但是GetMemory退出后p指向的空间就不在了,str指向的内容已经不是hello world了,变成了野指针。
void test()
{char* str = (char*)malloc(100);strcpy(str,"hello world");free(str);//这里已经释放了str指向的空间if(str != NULL){strcpy(str,"hello");//这里的str已经变成了野指针printf(str);}
}

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

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

相关文章

共享内存的分享

共享内存是一种进程间通信&#xff08;IPC&#xff0c;Inter-Process Communication&#xff09;的机制&#xff0c;用于在不同进程之间共享数据。它允许多个进程访问同一个内存段&#xff0c;从而使数据传递更加高效。共享内存的主要作用包括&#xff1a; 1. 高效数据交换 共…

java: 警告: 源发行版 8 需要目标发行版 8

前言 该文章中项目背景是&#xff1a;IDEA与设置的版本与实际电脑配置的不一致。也就是说只改了这个团队项目的JDK版本&#xff0c;IDEA上其它项目JDK版本未更改。 提示&#xff1a; IDEA警告&#xff1a;javaX&#xff1a;警告&#xff1a;源发行版 需要目标发行版 简略步…

.NET 某和OA办公系统全局绕过漏洞分析

转自先知社区 作者&#xff1a;dot.Net安全矩阵 原文链接&#xff1a;.NET 某和OA办公系统全局绕过漏洞分析 - 先知社区 0x01 前言 某和OA协同办公管理系统C6软件共有20多个应用模块&#xff0c;160多个应用子模块&#xff0c;从功能型的协同办公平台上升到管理型协同管理平…

qt 笔记

外部进程嵌入到Qt进程界面 将外部进程嵌入到 Qt 进程的界面中是一项复杂的任务&#xff0c;因为它涉及到操作系统特定的细节。在不同的操作系统上&#xff0c;这种嵌入方式可能会有所不同。以下是一些可能的方法和步骤&#xff0c;针对常见操作系统&#xff08;如 Windows 和 …

JS-06 原型式继承借用构造函数实现继承

目录 1 原型式继承 场景 前置问题 实现方法 2 借用构造函数实现继承 前置问题 错误的实现方式 正确的实现方式 1 原型式继承 场景 a、创建一个纯洁的对象&#xff1a;对象在控制台打印什么属性都没有 b、创建一个继承自某个父对象的子对象 前置问题 一个对象里有很…

基于稀疏辅助信号平滑的心电信号降噪方法(Matlab R2021B)

基于形态成分分析理论&#xff08;MCA&#xff09;的稀疏辅助信号分解方法是由信号的形态多样性来分解信号中添加性的混合信号成分&#xff0c;它最早被应用在图像处理领域&#xff0c;后来被引入到一维信号的处理中。 在基于MCA稀疏辅助的信号分析模型中&#xff0c;总变差方…

Python面试宝典:Python中与爬虫基础以及数据抓取和解析相关的面试笔试题(1000加面试笔试题助你轻松捕获大厂Offer)

Python面试宝典:1000加python面试题助你轻松捕获大厂Offer【第二部分:Python高级特性:第十七章:Python爬虫:第一节:爬虫基础以及数据抓取和解析】 第十七章:Python爬虫第一节:爬虫基础以及数据抓取和解析1. HTTP协议2. HTML/CSS/JavaScript3. 解析库4. Web开发者工具5.…

Python - list (append, extend, split)

Python list 与 Java 数组 Python list使用[]包裹&#xff0c;类似于Java 数组。不同点在于Python list元素可以是任意类型&#xff0c;Java 数组元组只能是基本数据类型之一。 >>> a [a, 2, [1,2]] >>> type(a) <class list> Python 声明了列表a&…

【前端篇】前端开发大厂面试真题

为助力小伙伴们梳理前端知识体系&#xff0c;从而能够充分地做好面试准备&#xff0c;那么今天就来给大家分享一份前端开发的面试真题与相关知识点&#xff0c;其中涵盖了最新版本的八股文&#xff08;包含最新的 Vue 3 面试题&#xff09;、高频算法题以及大佬的面经&#xff…

嵌入式进阶——EEPROM读写

&#x1f3ac; 秋野酱&#xff1a;《个人主页》 &#x1f525; 个人专栏:《Java专栏》《Python专栏》 ⛺️心若有所向往,何惧道阻且长 文章目录 设置EEPROM读写String字符串官方示例 EEPROM是一种可擦写可编程只读存储器&#xff08;Electrically Erasable Programmable Read-…

[ARM-2D 专题] arm-2d项目简介

Arm-2D 是一个用于 Cortex-M 处理器上的 2.5D 图像处理的开源项目。 由ARM公司开发和维护&#xff0c;属于官方性质的项目&#xff0c;目前最新版本为V1.1.6. 2022年7月发布1.0的预览版&#xff0c; 2023年2月发布第一个正式版V1.1.1&#xff0c;近一年来快速迭代和功能增强&a…

数据库-SQL优化下

Group by 优化 limit优化 count优化 UPDATE优化 当用id去更改的时候,一个是id1 另一个id2 这是没毛病的&#xff0c;因为加的是行锁 但是如果根据name 一个是where nameaa. 另一个是namehh,在另一个事务没提交前&#xff0c;另一个不能提交&#xff0c;因为name字段不是索引…

数据结构复习指导之B树和B+树

目录 B树和B树 考纲内容 1.B树及其基本操作 1.1B树的查找 1.2B树的高度&#xff08;磁盘存取次数&#xff09; 1.3B树的插入 1.4B树的删除 2.B树的基本概念 B树和B树 考纲内容 考研大纲对 B树和 B树的要求各不相同&#xff0c;重点在于考查B树&#xff0c;不仅要求理解…

基于51单片机数字频率计的设计

本文提出设计数字频率计的方案,重点介绍以单片机AT89C51为控制核心,实现频率测量的数字频率设计。测频的基本原理是采用在低频段直接测频法,在低频段直接测频法的设计思路,硬件部分由单片机和数计显示电路组成;软件部分由信号频率测量模块和数据显示模块等模块实现。应用单…

Raven2掠夺者2渡鸦2账号需要验证怎么解决 超简单验证账号教程

《渡鸦2》是一款源自韩国的创新力作&#xff0c;作为《Raven》系列的最新续篇&#xff0c;这款游戏在MMORPG手游领域内再度扩展了其标志性的暗黑奇幻宇宙&#xff0c;融入了大量革新的游戏设计与丰富内容。定档于2024年5月29日开启公测的《渡鸦2》&#xff0c;正处在紧张刺激的…

美光拟投巨资在日本广岛建DRAM厂,目标2027年底投产

美光科技&#xff08;Micron Technology&#xff09;据日本媒体报道&#xff0c;计划在日本广岛县新建一座DRAM芯片生产工厂&#xff0c;目标最快于2027年底投入运营。这一举措标志着美光在增强其内存芯片生产能力方面的又一重大步伐。 报道称&#xff0c;新工厂的总投资规模预…

Kotlin核心编程知识点-03-类型系统

文章目录 1.null 引用2.可空类型2.1.安全的调用 ?.2.2.Elvis 操作符 ?:2.3.非空断言 !!.2.4.类型检查2.5.类型智能转换 3.比 Java 更面向对象的设计3.1.Any&#xff1a;非空类型的根类型3.2.Any?: 所有类型的根类型3.3.自动装箱和拆箱3.4.数组类型 4.泛型&#xff1a;让类型…

C语言---扫雷游戏的实现

1.扫雷游戏的分析和设计 需要创建3个文件夹 test.c----扫雷游戏的测试 game.c----扫雷游戏的实现 game.h----扫雷游戏的实现 雷的信息使用二维数组存放 • 使⽤控制台实现经典的扫雷游戏 • 游戏可以通过菜单实现继续玩或者退出游戏 • 扫雷的棋盘是9*9的格⼦ • 默认…

CVPR2024《RMT: Retentive Networks Meet Vision Transformers》论文阅读笔记

论文链接&#xff1a;https://arxiv.org/pdf/2309.11523 代码链接&#xff1a;https://github.com/qhfan/RMT 引言 ViT近年来在计算机视觉领域受到了越来越多的关注。然而&#xff0c;作为ViT的核心模块--自注意力缺乏空间先验知识。此外&#xff0c;自注意力的二次计算复杂度…

C++:类

①引用 ②函数重载 函数重载:函数名相同,函数参数类型和顺序不同,仅函数返回值不同不能视为函数重载。 函数重载体现函数的多态性(即一个接口,多种方法),程序在编译时决定调用函数。是面向对象编译领域的核心,属于C++的静态绑定。 作用:方便 与默认参数一起使用时…