【C】动态内存管理详解

news2024/11/19 19:34:59

动态内存管理

  • C/C++内存开辟区域
    • 为什么存在动态内存分配?
  • 动态内存函数的介绍(在头文件stdlib.h中)
    • malloc
    • free
    • calloc
    • realloc
  • 常见的动态内存的错误
    • 对NULL解引用操作
    • 对动态开辟的空间进行越界访问
    • 对非动态开辟的空间进行free释放
    • 使用free释放动态开辟内存的一部分
    • 对同一块动态内存进行多次释放
    • 动态开辟的内存忘记释放

C/C++内存开辟区域

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

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

在这里插入图片描述

为什么存在动态内存分配?

我们平常创建的变量和数组都是在栈上创建的,但是他们开辟的空间有2个特点:

空间开辟大小是固定的。
数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配。

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

动态内存函数的介绍(在头文件stdlib.h中)

malloc

void* malloc (size_t size);

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

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

free

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

void free (void* ptr);

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

如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
如果参数 ptr 是NULL指针,则函数什么事都不做。

代码演示:

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

int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL) //这里的判断是很有必要的
	{
		return;
	}
	for (int i = 0; i < 10; i++)
	{
		p[i] = 0 + i;
	}
	free(p);
	p = NULL; //防止出现野指针
	return 0;
}

calloc

void* calloc (size_t num, size_t size);

calloc 函数也用来动态内存分配。

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

calloc在开辟空间的时候会将每个字节都初始化为0,而malloc只是申请空间并不会初始化。我们来举个例子:

#include <stdio.h>
#include <stdlib.h>
int main()
{
	int* pa = (int*)malloc(40);
	if (pa == NULL)
	{
		perror("malloc");
		return;
	}
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d\n", pa[i]);
	}
	free(pa);
	pa = NULL;
	int* pb = (int*)calloc(10, 4);
	if (pb == NULL)
	{
		perror("malloc");
		return;
	}
	for (i = 0; i < 10; i++)
	{
		printf("%d ", pb[i]);
	}
	free(pb);
	pb = NULL;
	return 0;
}

我们来看一下运行结果:
在这里插入图片描述
可以看到malloc开辟空间是不初始化的,而calloc开辟空间是初始化位为0的。

realloc

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

函数原型:

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

realloc在调整内存空间的是存在两种情况:
情况1:原有空间之后有足够大的空间
情况2:原有空间之后没有足够大的空间

当是情况1的时候,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化。

是情况2 的时候,原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另找一个合适大小的连续空间来使用。这样函数返回的是一个新的内存地址。

针对上面的两种情况我们在使用realloc是就要注意一点了,不能直接将realloc扩增的起始地址付给原本的空间,万一开辟失败原本的空间就变得无法控制,导致内存泄漏。例如:

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

int main()
{
	//开辟空间
	int* pa = (int*)malloc(40);
	//判断有效性
	if (pa == NULL)
	{
		perror("malloc");
		return;
	}
	//使用
	for (int i = 0; i < 10; i++)
	{
		pa[i] = i;
	}
	//扩容
	int* ptr = (int*)realloc(pa, 60);
	//判断是否开辟成功
	if (ptr == NULL)
	{
		perror("realloc");
		free(pa);
		pa = NULL;
		return;
	}
	//成功将起始地址给pa
	pa = ptr;
	//使用
	for (int i = 0; i < 15; i++)
	{
		printf("%d ", pa[i]);
	}
	//使用完后释放空间
	free(pa);
	pa == NULL;
	return 0;
}

总而言之,我们在开辟内存后一定要判断有效性,否则会出现对空指针的解引用,导致程序崩溃。

常见的动态内存的错误

对NULL解引用操作

例如:

void test()
{
	int* p = (int*)malloc(INT_MAX / 4);
	*p = 20;
	free(p);
}

如果p是NULL就会出现问题。

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

例如:

void test()
{
	int* pa = (int*)malloc(40);
	//判断有效性
	if (pa == NULL)
	{
		perror("malloc");
		return;
	}
	//使用
	for (int i = 0; i <= 10; i++)
	{
		pa[i] = i;
	}
}

这里当 i = 10 时就会出现问题。

对非动态开辟的空间进行free释放

例如:

int main()
{
	int a = 0;
	int* pa = &a;
	free(pa);
	return 0;
}

这种写法也是错误的,free释放的是动态开辟来的空间。

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

例如:

void test()
{
	int* p = (int*)malloc(40);
	p++;
	free(p);
}

此时p已经不再指向起始位置了。

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

例如:

void test()
{
	int* p = (int*)malloc(40);
	free(p);
	free(p);
}

这里也是非常严重的错误。

动态开辟的内存忘记释放

例如:

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


int main()
{
	test();
	while (1);
}

忘记释放不再使用的动态开辟的空间会造成内存泄漏。
动态开辟的空间一定要释放,并且正确释放 。

今天的分享就到这里,感谢大家的关注和支持。

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

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

相关文章

探索Python异常:让程序不再崩溃!

文章目录 前言什么是异常捕获异常基本语法捕获指定类型异常捕获多个指定类型的异常捕获异常描述信息捕获所有异常elsefinally 异常的嵌套自定义异常结语 前言 在编程的世界中&#xff0c;我们常常会面对各种各样的错误和异常情况。尤其当我们使用Python这样的高级编程语言时&a…

4. 设计测试用例 (一) 等价类 边界值 判定表

目录 1. 设计测试用例的基本要素 1.1 测试用例概念 1.2 测试用例要素 1.3 测试用例的重要性 2. 测试用例设计方法 2.1 基于需求设计测试用例 步骤 2.2 练习设计测试用例 2.3 具体设计测试用例方法 2.3.1 等价类 设计测试用例步骤 举例 2.3.2 边界值 设计测试用例…

PowerDesigner 数据库建模使用详解

目录 一、前言 二、PowerDesigner概述 2.1 PowerDesigner核心能力 2.1.1 集成多种建模能力 2.1.2 自动生产代码能力 2.1.3 强大的逆向工程能力 2.1.4 可扩展的企业库解决方案 2.2 PowerDesigner常用的几种模型 2.2.1 概念模型 2.2.2 逻辑数据模型 2.2.3 物理模型 2.2…

综合能源系统(2)——综合能源系统典型应用场景

综合能源系统关键技术与典型案例  何泽家&#xff0c;李德智主编 根据空间分布范围特征&#xff0c;综合能源系统可大致划分为楼宇级、园区级以及区域级。楼宇级综合能源系统适用于办公楼、家庭、商场等单一主体区域&#xff0c;投资、建设和运营模式较为简单&#xff0c;技术…

arm学习stm32芯片学习方波启动蜂鸣器,马达,风扇,裸机开发,soc

main.c #include "pwm.h" extern void printf(const char *fmt, ...); void delay_ms(int ms) {int i,j;for(i 0; i < ms;i)for (j 0; j < 1800; j); } int main() {//蜂鸣器初始化hal_pwm_beep_init1();//马达hal_pwm_motor_init1();//风扇hal_pwm_blower_…

基于Vue3+LeaderLine实现画线测距及线条自由调整

先看下效果&#xff1a;我们画线后可以根据比例关系自动计算距离&#xff0c;并且线条不对可以自由调整 <template><div id"image-detail"><el-image :src"myImageUrl" style"height: auto; width: 800px;" fit"scale-dow…

Django实现接口自动化平台(十)自定义action names【持续更新中】

相关文章&#xff1a; Django实现接口自动化平台&#xff08;九&#xff09;环境envs序列化器及视图【持续更新中】_做测试的喵酱的博客-CSDN博客 深入理解DRF中的Mixin类_做测试的喵酱的博客-CSDN博客 python中Mixin类的使用_做测试的喵酱的博客-CSDN博客 本章是项目的一…

高数笔记5(第一章函数 极限 连续-第三节-函数的连续性)

目录 第三节 函数的连续性&#xff08;1&#xff09;函数的连续性例1&#xff08;补充定义&#xff0c;函数连续&#xff09;例4&#xff08;无穷小*有界量&#xff09;例6 &#xff08;补充定义&#xff0c;三角函数的代换的妙用&#xff09; &#xff08;2&#xff09;连续函…

7.带你入门matlab偏斜度和峰度(matlab程序)

峰度&#xff08;Kurtosis&#xff09;与偏态&#xff08;Skewness&#xff09;就是量测数据正态分布特性的两个指标。 峰度&#xff08;Kurtosis&#xff09; 峰度衡量数据分布的平坦度&#xff08;flatness&#xff09;&#xff0c;即数据取值分布形态陡缓程度的统计量。它…

C#,数值计算——柯西分布(Cauchy distribution)的计算方法与源程序

柯西分布&#xff08;Cauchy distribution&#xff09;简介 The Cauchy distribution, also called the Lorentzian distribution or Lorentz distribution, is a continuous distribution describing resonance behavior. It also describes the distribution of horizontal …

ASEMI快恢复二极管MUR2080CTR资料,MUR2080CTR参数

编辑-Z MUR2080CTR是一种高压、超快恢复整流二极管。它通常用于各种电子应用&#xff0c;如电源、逆变器和电机控制电路。该二极管设计用于处理高压和高频开关&#xff0c;适用于需要快速高效整流的应用。 MUR2080CTR二极管的一个关键特性是其超快的恢复时间。这意味着它可以非…

从零开始 Spring Cloud 2:Eureka 注册中心

从零开始 Spring Cloud 2&#xff1a;Eureka 注册中心 图源&#xff1a;laiketui.com Eureka 常被用作 Spring Cloud 的注册中心&#xff0c;用于注册微服务的接口提供方。 在上一篇文章中&#xff0c;我们实现了两个子模块互相调用接口&#xff0c;但存在一个缺陷&#xff0…

旅游管理系统的设计与实现(论文+源码)_kaic

摘 要 旅游业走过了改革开放&#xff0c;到现在依旧蓬勃发展。但是放眼国际社会&#xff0c;我们在旅游业发展的深度和广度上所做的努力还远远不够。在中国&#xff0c;旅游业也将成为经济崛起中的重要一环。目前&#xff0c;我们生活在一个信息时代里。无论是工作&#xff0c;…

哈希表的原理

哈希概念 线性表、树结构的查找方式都是以关键字的比较为基础&#xff0c;查找效率比较低&#xff0c;顺序表的时间复杂度是O&#xff08;n&#xff09;&#xff0c;平衡树中为树的高度&#xff0c;即O&#xff08;logn&#xff09;&#xff0c;搜素的效率取决于搜索过程的元素…

归并排序的递归和非递归

基本思想 归并排序&#xff08;MERGE-SORT&#xff09;是建立在归并操作上的一种有效的排序算法,该算法是采用分治法&#xff08;Divide andConquer&#xff09;的一个非常典型的应用。将已有序的子序列合并&#xff0c;得到完全有序的序列&#xff1b;即先使每个子序列有序&a…

OpenCV项目开发实战--详细介绍如何进行边缘轮廓检测 (Python/C++)-附源码

使用轮廓检测​​,我们可以检测对象的边界,并轻松在图像中定位它们。它通常是许多有趣应用的第一步,例如图像前景提取、简单图像分割、检测和识别。 因此,让我们使用 OpenCV 来了解轮廓和轮廓检测,并亲眼看看如何使用它们来构建各种应用程序。 轮廓在计算机视觉中的应用

latex2【图片、公式、矩阵】

图片 语法&#xff1a; \includegraphics{排队论模型.png} 看起来很别扭是吧&#xff0c;需要进行“修饰”&#xff1a; 当然&#xff0c;这样也很丑&#xff0c;一般写论文可以用以下的格式&#xff1a; \begin{figure}[H] \caption{问题一模型示意图} \label{paiduimx} …

【企业架构工具】2023 年 18 大企业架构工具

这些流行和新兴的 EA 工具为企业提供了支持企业架构和数字化转型所需的一切。 企业架构系统并不总是必不可少的。据推测&#xff0c;在 1940 年代&#xff0c;国际商业机器公司的一位领导人小托马斯沃森 (Thomas Watson Jr.) 曾说过&#xff1a;“我认为大约有 5 台计算机的全球…

基于SpringBoot+vue的校园疫情防控系统设计与实现

博主介绍&#xff1a; 大家好&#xff0c;我是一名在Java圈混迹十余年的程序员&#xff0c;精通Java编程语言&#xff0c;同时也熟练掌握微信小程序、Python和Android等技术&#xff0c;能够为大家提供全方位的技术支持和交流。 我擅长在JavaWeb、SSH、SSM、SpringBoot等框架…

Linux内核网络-拥塞控制系列(一)

谈起网络拥塞控制&#xff0c;大家可能很熟悉八股文中的"加法增大“、”乘法减小“、”慢开始“、“拥塞避免”、“快重传”、“快恢复”等概念。没错&#xff0c;这是一种经典网络拥塞控制算法的基础理论&#xff0c;但在实际的实现时不同的拥塞控制算法&#xff0c;有很…