梦开始的地方—— C语言动态内存管理(malloc+calloc+realloc+free)

news2024/11/17 21:44:38

文章目录

  • 动态内存管理
    • 1.为什么需要动态内存分配?
    • 2. 动态内存函数
      • malloc&free
      • calloc
      • realloc
    • 3. 常见的动态内存错误
      • 对NULL解引用
      • 对动态开辟空间的越界访问
      • 对非动态开辟内存使用free释放
      • 使用free释放一块动态开辟内存的一部分
      • 对同一块动态内存多次释放
      • 动态开辟内存忘记释放(内存泄漏)
    • 4. 经典错误题目
      • 题目1
      • 题目2
      • 题目3
      • 题目4
    • 5. C/C++程序的内存开辟

动态内存管理

1.为什么需要动态内存分配?

创建变量或者数组就是我们常见的内存开辟方式

int num = 100;//开辟4个字节的空间
int arr[10] = {0};//开辟40个字节的连续空间

上面开辟空间的方式有两个特点:

  1. 开辟的空间大小是固定的,也就是不能修改的。
  2. 数组在声明的时候,必须知道数组长度,它所需要的内存在编译时就已经分配好了。

但是很多时候,我们对于空间的需求上面的两种情况是满足不了的,有的时候我们需要的内存大小要程序运行之后才能知道,或者说有时候数组大小空间不够了,那么数组编译时开辟的内存空间的方式就不可行了,这个时候就需要动态内存开辟了。

注意:动态开辟的内存是在堆上的,而我们使用的局部变量和函数的形参是在栈上开辟空间。

2. 动态内存函数

malloc&free

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

mallo函数是C语言提供的动态内存开辟的函数

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

void* malloc (size_t size);
  • 如果空间开辟成功,则返回一个指向开辟好空间起始地址的指针
  • 如果开辟失败,则返回一个NULL指针,所以该函数的返回值一定要做判断
  • 该函数的返回值是void*,所以malloc函数并不知道开辟空间的类型,要使用者来决定这块空间用来存放什么
  • 如果size参数给0,这是C语言标准未定义的,取决于编译器的实现
  • 开辟的是size个字节的空间

C语言还提供了一个和malloc函数成对出现的函数free,用来释放动态开辟的的内存空间

void free (void* ptr);
  • 如果参数ptr指向的空间不是动态开辟的,那么对于free函数来说这是为定义的
  • 如果参数ptr是NULL,则函数什么事都不做

代码示例

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main()
{
	int arr[10] = { 0 };//在栈区申请了40个字节的空间
	int* ptr = (int*)malloc(40);//在堆区申请了40个字节的空间
	if (ptr == NULL)
	{
        
        //strerror会返回错误信息
		printf("开辟内存失败 %s", strerror(errno));
	}
	else
	{
		int i = 0;
		for (i = 0; i < 10; i++)
		{
			*(ptr + i) = 0;
		}
	}
	//初始化为0
	
	free(ptr);//释放空间
	ptr = NULL;//指向NULL
	return 0;
}
  • 我们这里定义的arr数组是存放在栈区的
  • 而动态开辟的空间是在堆区申请的空间
  • 动态开辟内存的时候一定记得判断是否开辟成功
  • malloc开辟的空间如果不收到调用free函数释放,在程序结束的时候会由系统帮我们自动释放。
  • 但如果程序是在服务器上一直运行,老是申请内存又不释放,就会造成内存泄露。就可能导致服务器出问题,甚至宕机。所以当我们申请的内存不在使用时,就一定要记得释放。
  • 当内存释放后,也要记得把指针指向NULL,让这个指针失忆。虽然内存空间被释放了,但指针ptr还记得那块空间的地址是非常危险的一件事情,可能会出现误操作。所以内存释放后记得把指针指向空

calloc

C语言还提供了一个动态内存分配的函数calloc

void* calloc (size_t num, size_t size);
  • 该函数的作用是开辟num个大小为size的元素开辟一块连续的内存空间,并把空间的每一个字节初始化为0
  • 该函数和malloc的区别只在于calloc会在返回指针之前,把申请空间的每个字节初始化为全0

代码示例

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main()
{
	int* ptr = calloc(1,sizeof(int));//在堆区申请了4个字节的空间
	if (ptr == NULL)
	{
		printf("开辟内存失败 %s", strerror(errno));
	}
	else
	{

	}
	
	free(ptr);//释放空间
	ptr = NULL;//指向NULL
	return 0;
}

在这里插入图片描述

如果我们对申请的内存空间的内容要求初始化,那么可以使用calloc函数

realloc

realloc函数则更加灵活,有的时候我们会发现申请的空间太小了有时候又会绝对申请的空间太大了。我们就会对内存的大小进行跳转,而realloc函数就可以对动态开辟内存进行调整。

void* realloc (void* ptr, size_t size);
  • ptr是要调整内存的起始地址
  • size是调整后的大小
  • 返回值就是调整后内存的起始地址
  • 这个函数在调整内存空间大小的基础上,还有可能将原理内存中的数据移动到新的空间
  • 也就是说realloc对内存大小的调整存在两种情况

在这里插入图片描述

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

所以在使用realloc函数的时候就需要注意

调整空间大小时需要新建一个指针变量来接收返回值,避免内存空间开辟失败。从而导致原理的空间丢失。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main()
{
	int* ptr = (int*)malloc(10);
	if (ptr == NULL)
	{
		printf("开辟内存失败 %s", strerror(errno));
	}
	else
	{

	}
	//调整空间
	int* p = realloc(ptr, 40);//调整成40个字节大小
	if (p == NULL)
	{
		printf("调整内存空间失败 %s", strerror(errno));
	}
	else
	{
		ptr = p;
		p = NULL;
	}
	free(ptr);//释放空间
	ptr = NULL;//指向NULL


	return 0;
}

3. 常见的动态内存错误

对NULL解引用

当开辟内存过大时,ptr是有可能为NULL指针的,当为NULL的时候,*ptr非法访问内存

#include <stdio.h>
#include <stdlib.h>
int main()
{
	int* ptr = (int*)malloc(INT_MAX);
	*ptr = 100;

	free(ptr);//释放空间
	ptr = NULL;//指向NULL


	return 0;
}

在这里插入图片描述

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

越界访问也是会出现问题的

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
	int* ptr = (int*)malloc(12);
	if (ptr == NULL)
	{
		printf("开辟内存失败 %s", strerror(errno));
	}
	else
	{
		int i = 0;
		for (i = 0; i < 10; i++)
		{
			//越界访问
			*(ptr + i) = 100;
		}
	}

	free(ptr);//释放空间
	ptr = NULL;//指向NULL


	return 0;
}

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

使用free是否非动态开辟的内存空间,也是错误的。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
	int* ptr = (int*)malloc(12);
	int arr[10] = { 0 };
	if (ptr == NULL)
	{
		printf("开辟内存失败 %s", strerror(errno));
	}
	

	free(arr);//释放非动态开辟的空间
	ptr = NULL;//指向NULL


	return 0;
}

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

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
	int* ptr = (int*)malloc(12);
	if (ptr == NULL)
	{
		printf("开辟内存失败 %s", strerror(errno));
	}
	else
	{
		//此时ptr已经不指向动态开辟内存空间的起始位置了
		ptr+=2;
	}
	

	free(ptr);//释放空间
	ptr = NULL;//指向NULL


	return 0;
}

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

多次释放同一块内存空间也是会有问题的

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
	int* ptr = (int*)malloc(12);
	if (ptr == NULL)
	{
		printf("开辟内存失败 %s", strerror(errno));
	}
	else
	{
		//第一次释放
		free(ptr);
	}
	

	free(ptr);//再次释放
	ptr = NULL;//指向NULL


	return 0;
}

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

只申请不释放,会导致内存泄露,导致那些内存无法被使用。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void test()
{
	int* ptr = (int*)malloc(12);
	if (ptr == NULL)
	{
		printf("开辟内存失败 %s", strerror(errno));
	}
}
int main()
{
	
	while (1)
	{
		test();
	}


	return 0;
}

4. 经典错误题目

题目1

这是一道非常经典的题目,这段代码存在这一下这个问题

  • malloc动态开辟的空间未进行释放会造成内存泄露
  • 传递给GetMemory函数的是形参变量str,形参是实参的一份临时拷贝。所以p只是一个局部变量,对它的修改并不会影响str
  • 当str为NULL时,strcpy函数就会出问题,如果str为NULL,printf打印时也会出现问题
#include <stdio.h>
#include <string.h>
void GetMemory(char* p)
{
    p = (char*)malloc(100);
}
void Test(void)
{
    char* str = NULL;
    GetMemory(str);
    strcpy(str, "hello world");
    printf(str);
}
int main()
{
    Test();
    return 0;
}

这段代码应该这么修改

  • 取出str的地址,拿二级指针接收,解引用就能修改str了。
  • 使用完动态开辟的内存后,要进行释放。
  • 不要一维函数销毁了内存就会自动释放,函数是在栈上开辟空间,而malloc函数是从堆上申请空间
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
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;
}

题目2

这段代码打印的是啥也不确定

  • 因为p是一个局部的变量,当GetMemory函数调用结束了,p数组的声明周期也结束了。
  • p所指向的这一块空间就会被释放,归还给操作系统。
  • 当p作为返回值返回,str接收了这个返回值。
  • 虽然str指向的是p数组首元素地址,当拿块空间已经被释放,归还给操作系统了。那么此时这块空间就可能被其它程序使用
  • 所以虽然str指向首元素地址,但打印出来的可以不一定是"hello world"
#include <stdio.h>
#include <string.h>
#include <stdlib.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;
}

题目3

这道题目典型的内存泄露问题,没有对动态开辟的的内存进行释放

#include <stdio.h>
#include <string.h>
#include <stdlib.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;
}

稍微修改一下就好了

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

    return 0;
}

题目4

这道题目最大的问题就是对释放后的动态开辟内存再次使用

  • 虽然free把开辟的内存释放了,但str是依旧指向那一块空间的起始地址的
  • 再使用strcpy进行字符串拷贝,这就属于非法内存访问了。
  • 所以free完后,记得把指针置为NULL
#include <stdio.h>
#include <string.h>
#include <stdlib.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;
}

这段代码写的非常古怪,但还是可以修改的。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

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

    return 0;
}

5. C/C++程序的内存开辟

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

  • 栈区:在执行函数时,函数局部变量的存储单元都可以在栈上创建,当函数执行完毕后这些存储单元会被自动释放。栈区分配运算内置于处理器的指令集中,效率很高,但是分配的内存空间是有限的。栈区主要存放的是局部变量、函数参数(形参)、返回数据、返回地址。
  • 堆区:一般由程序员来释放,程序结束时可能由操作系统来帮程序员释放。像malloc就是在堆上开辟空间。
  • 静态区:存放全局变量、静态数据,程序结束时由系统释放
  • 代码段:存放函数体(类成员和全局函数)的二进制代码

在这里插入图片描述


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

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

相关文章

Vue实现模糊查询:filter()

需求&#xff1a;在输入框里输入内容&#xff0c;包含相关内容的值被筛选出来&#xff1b; 图示&#xff1a; 最初的代码&#xff1a; <body><div id"box"><input type"text" input"handleInput()" v-model"mytext"&…

计算机网络 - 网络层 选择填空判断复习题

一. 单选题&#xff08;共25题&#xff0c;80分&#xff09; (单选题) 以下( )协议完成了从网卡到IP地址的映射。 A A.ARP协议 B.RARP协议 C.IGMP协议 D.ICMP协议 (单选题) 一个C类地址,采用了255.255.255.240作为子网掩码,那么这个C类地址可以划分为( )个子网。 A A.16 B.32 …

零基础自学javase黑马课程第十四天

零基础自学javase黑马课程第十四天 ✨欢迎关注&#x1f5b1;点赞&#x1f380;收藏⭐留言✒ &#x1f52e;本文由京与旧铺原创&#xff0c;csdn首发&#xff01; &#x1f618;系列专栏&#xff1a;java学习 &#x1f4bb;首发时间&#xff1a;&#x1f39e;2022年11月21日&…

【Hack The Box】Linux练习-- FriendZone

HTB 学习笔记 【Hack The Box】Linux练习-- FriendZone &#x1f525;系列专栏&#xff1a;Hack The Box &#x1f389;欢迎关注&#x1f50e;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; &#x1f4c6;首发时间&#xff1a;&#x1f334;2022年11月17日&#x1f334; &#…

秋招失足,拿到这份“Java 高分指南(25 专题)”,金三银四翻盘有望

面试造火箭&#xff0c;工作拧螺丝&#xff01;金九银十灰溜溜地落榜&#xff0c;备受打击。正当准备明年金三银四之际&#xff0c;意外喜提朋友赠送的这“Java 高分指南&#xff08;25 专题&#xff09;”&#xff1a;Elasticsearch、微服务、Linux、JavaOOP、集合/泛型、Mysq…

move_base代码解析(一)MoveBase::executeCb

move_base是ROS中的经典路径规划算法&#xff0c;move_base 功能包提供了基于动作(action)的路径规划实现&#xff0c;move_base 可以根据给定的目标点&#xff0c;控制机器人底盘运动至目标位置&#xff0c;并且在运动过程中会连续反馈机器人自身的姿态与目标点的状态信息。 …

HTTP协议中的HTTP报文

HTTP中的HTTP报文 1、HTTP报文信息 1.1定义 用于HTTP协议交互的信息叫做HTTP 报文。 HTTP 报文大致可分为报文首部和报文主体两块。两者由最初出现的空行&#xff08;CRLF&#xff09;来划分&#xff08;通常并不一定要有报文主体&#xff09;。 1.2请求报文和响应报文 请…

CSS13_由html{height: 100%} 引发的CSS百分比宽高度的思考

一、html, body { height:100% } CSS有一个常见设置&#xff1a;html,body{ height:100% }&#xff0c;可能大家已经熟视无睹了&#xff0c;但细细思索&#xff0c;可能会有些奇怪&#xff0c;为什么html还需要设置height:100%呢&#xff1f;html不就代表整个页面了吗&#xf…

[附源码]java毕业设计网上书店管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

SpringBoot项目使用JSP

SpringBoot不推荐使用JSP,而是使用模板技术代替JSP视图 先创建个SpringBoot项目 使用JSP需要如下配置 加入一个处理JSP的依赖(使用该JSP依赖原因是SpringBoot的jar包是内嵌了一个Tomcat因此要用这个Jar包,如果你的SpringBoot需要打成war包,不使用内嵌Tomcat,可用通用的JSP依赖…

深度学习基础--神经网络(3)神经网络的学习,损失函数初识

文章目录神经网络的学习从数据中学习数据区分损失函数均方误差交叉熵误差mini-batch学习损失函数更新的思路本文为学习笔记整理参考书籍&#xff1a;《深度学习入门 : 基于Python的理论与实现 》/ (日) 斋藤康毅著 ; 陆宇杰译. – 北京 : 人民邮电出版社, 2018.7&#xff08;20…

Android插件式换肤以及资源加载流程分析

前言 APP更换皮肤的方式有很多&#xff0c;如系统自带的黑夜模式、插件换肤、通过下发配置文件加载不同主题等等&#xff0c;我们这里就浅谈下插件换肤方式。想实现插件换肤功能&#xff0c;我们就需要先弄清楚 :APP是如何完成资源加载的。 资源加载流程 这里我们以ImageVie…

植物大战僵尸变态辅助开发系列教程(E语言实现和VC6实现)(中)

植物大战僵尸变态辅助开发系列教程&#xff08;E语言实现和VC6实现&#xff09;&#xff08;中&#xff09;26、第一种方法实现变态加速功能27、第二种方法找出变态攻击加速的方法28、加快阳光、金币生产速度29、全屏僵尸29、全屏减速第一课30、全屏减速第二课31、全屏奶油的找…

(Transferrin)TF-PEG-DBCO/TCO/tetrazine 转铁蛋白-聚乙二醇-二苯基环辛炔/反式环辛烯/四嗪

产品名称&#xff1a;转铁蛋白-聚乙二醇-二苯基环辛炔 英文名称&#xff1a;(Transferrin)TF-PEG-DBCO 质量控制&#xff1a;95% 原料分散系数PDI&#xff1a;≤1.05 存储条件&#xff1a;-20C&#xff0c;避光&#xff0c;避湿 用 途&#xff1a;仅供科研实验使用&#xff0c…

Android App开发动画特效中插值器和估值器的讲解以及利用估值器实现弹幕动画实战(附源码和演示视频 可直接使用)

需要图片集和源码请点赞关注收藏后评论区留言~~~ 一、插值器和估值器 插值器用来控制属性值的变化速率&#xff0c;也可以理解为动画播放的速度&#xff0c;默认是先加速再减速。若要给动画播放指定某种速率形式&#xff0c;调用setInterpolator方法设置对应的插值器实现类即可…

Spring Boot 分离配置文件的 N 种方式

今天聊一个小伙伴在星球上的提问&#xff1a; 问题不难&#xff0c;解决方案也有很多&#xff0c;因此我决定撸一篇文章和大家仔细说说这个问题。 1. 配置文件位置 首先小伙伴们要明白&#xff0c;Spring Boot 默认加载的配置文件是 application.properties 或者 application…

【云计算大数据_牛客_Hbase】选择/判断——Hbase

1.Hive 1.下面关于Hive metastore的三种模式的描述错误的是() Derby方式是内嵌的方式,也是默认的启动方式,一般用于单元测试local模式中,使用MySQL 本地部署实现metastoreremote模式为远程MySQLDerby方式在同一时间只能有多个进程连接使用数据库 2. 百度文库 2、代码sel…

Android App开发中集合动画和属性动画的讲解及实战演示(附源码 简单易懂 可直接使用)

需要图片集和源码请点赞关注收藏后评论区留言~~~ 一、集合动画 有时一个动画效果会加入多种动画&#xff0c;比如一个旋转一边缩放&#xff0c;这时便会用到集合动画AnimationSet把几个补间动画组装起来&#xff0c;实现让某视图同时呈现多种动画的效果 因为集合动画和补间动…

Jetson Orin使用Yolo5开源数据集训练模型检测口罩

软硬件环境&#xff1a; 乌班图 20.04 64位蟒蛇与 3.8.10英伟AGX Orin库达11.4PyTorch1.12YOLOv5-6.1感谢开源数据集下载地址 正常都是自己收集完了训练&#xff0c;今天就省略这个步骤了。 如果想自己制作看下面的流程。 软硬件环境搭建教程链接 刷机的话使用官方教程或者…

DNS协议

DNS服务器 人类更喜欢记忆主机名&#xff0c;而路由器更喜欢定长的、有结构层次的IP地址&#xff0c;DNS应运而生&#xff1a;DNS能进行主机名到IP地址转换的目录服务。 DSN是&#xff1a; &#xff08;1&#xff09;一个由分层的DNS服务器&#xff08;DNS server&#xff09;实…