C动态内存管理

news2024/11/16 3:24:50

前言:不知不觉又过去了很长的一段时间。今天对C语言中的动态内存管理进行一个系统性的总结。

1 为什么要有动态内存分配

C语言中,使用int,float,double,short等数据内置类型以及数组不是也可以开辟内存空间吗?为什么还要有动态内存分配呢?这是因为以上方式开辟出来的内存空间有两个缺点

. 空间开辟大小是固定的

. 数组在声明的时候,必须指定数组的长度,数组空间一旦确定了大小无法调整

但是对于空间的需求不仅仅是上述情况。有时候我们需要的空间大小在程序运行起来的时候才能知道。那么上述开辟空间的方式就不能满足了。因此C语言中引入了动态内存分配,,让程序员自己申请,释放空间,相对灵活。

2 malloc和free

2.1 malloc函数的介绍

//返回值类型是void*指针,参数类型是size_t,size是申请内存块的大小,单位是字节
//size_t是一个unsigned int类型
void* malloc(size_t size);

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

. 如果开辟成功,则返回一个指向开辟好空间的指针**。

. 如果失败,则返回一个NULL指针,因此malloc函数的返回值一定要做检查

. malloc函数的返回类型是void*类型的指针所以malloc函数并不知道开辟空间的类型,使用的时候由使用者自己来决定

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

2.2 free函数的介绍

C语言提供了一个free函数,是专门用来释放,回收动态开辟出来的内存空间

//返回值类型是void,参数类型是void*,ptr是指向先前由malloc或realloc或calloc分配的内存空间
void free(void* ptr);

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

. 如果参数ptr指向的空间不是动态开辟的,那么free函数的行为是未定义的

. 如果参数ptr是NULL指针,那么free函数什么事都不做

#include<stdio.h>
#include<stdlib.h>
int main()
{
	//动态申请内存空间
	int* ptr = (int*)malloc(10 * sizeof(int));
	//检查是否开辟成功
	if (NULL == ptr)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	for (int i = 0; i < 10; i++)
	{
		*(ptr + i) = i + 1;
	}
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", *(ptr + i));
	}
	//释放空间
	free(ptr);
	//改变ptr指针的指向
	ptr = NULL;
	return 0;
}

3 calloc和realloc

3.1 calloc函数的介绍

void* calloc(size_t num,size_t size);

calloc函数也是用来动态开辟内存的

. calloc函数的功能是为num个大小为size的元素开辟一块空间,并把空间的每个字节初始化为0

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

#include<stdio.h>
#include<stdlib.h>
int main()
{
	//动态开辟10*sizeof(int)个字节
	int* p = (int*)calloc(10, sizeof(int));
	//检查是否开辟成功
	if (NULL == p)
	{
		printf("calloc fail\n");
		exit(-1);
	}
	//观察calloc函数开辟空间的内容
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", *(p + i));
	}
	free(p);
	p=NULL;
	return 0;
}

3.2 realloc函数的介绍

void* realloc(void* ptr,size_t size);

realloc函数的功能是对空间的大小进行调整

. ptr是要调整的内存地址

. size是调整之后新的大小

. 返回值是调整之后内存空间的起始地址

. 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的内存空间

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

1.原有内存空间之后有足够的空间

2.原有内存空间之后没有足够大的空间

在这里插入图片描述


情况1:在原有空间之后追加空间,原有空间的数据不发生变化

情况2:原有空间之后没有足够的空间,扩展的方法是:在堆空间上找一个合适大小的连续空间来使用,并将原有空间的数据拷贝一份给新空间,释放原有空间,返回一个新的地址

#include<stdio.h>
#include<stdlib.h>
int main()
{
	int* ptr = (int*)calloc(20);
	if (NULL == ptr)
	{
		printf("calloc fail\n");
		exit(-1);
	}
	//如果扩容失败会怎么样
	//原有数据也会丢失,不推荐这种写法
	ptr = realloc(ptr, 40);//ok?
	//这种写法更为安全
	int* tmp = (int*)realloc(ptr, 40);
	if (NULL == tmp)
	{
		printf("realloc fail\n");
		exit(-1);
	}
	ptr = tmp;
	free(ptr);
	ptr = NULL;
	return 0;
}

4 常见的动态内存错误

4.1 对NULL指针的解引用操作

void test()
{
	int* p = (int*)malloc(sizeof(int));
	if (NULL == p)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	*p = 20;//如果p是NULL,就会有问题
	free(p);
	p = NULL;
}

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

void test()
{
	int* p = (int*)malloc(sizeof(int)*10);
	if (NULL == p)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	for (int i = 0; i <= 10; i++)
	{
		*(p + i) = i;//i为10的时候就越界访问了
	}
	free(p);
	p = NULL;
}

4.3 释放一部分动态开辟空间

void test()
{
	int* p = (int*)malloc(sizeof(int) * 10);
	if (NULL == p)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i;
	}
	for (i = 0; i < 5; i++)
	{
		printf("%d ", *p);
		p++;
	}
	//此时p不再指向动态开辟空间的起始地址,只释放了一部分空间,p就是野指针
	free(p);
	p = NULL;
}

4.4 对非动态开辟空间的释放

void test()
{
	int a = 10;
	int* p = &a;
	//对非动态开辟内存的释放
	free(p);
	p = NULL;
}

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

void test()
{
	int* p = (int*)malloc(sizeof(int) * 10);
	if (NULL == p)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	free(p);
	free(p);//重复释放
	p = NULL;
}

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

void test()
{
	int* p = (int*)malloc(sizeof(int) * 10);
	if (NULL == p)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	//忘记释放
}

5 动态内存经典笔试题解析

5.1 题目一:

#include<stdio.h>
#include<stdlib.h>
void GetMemory(char* p)
{
	//动态开辟空间未进行释放,内存泄露
	p = (char*)malloc(100);
}
void Test(void)
{
	char* str = NULL;
	//str作为参数,这里传递的是NULL,形参的改变不会影响实参
	GetMemory(str);
	//因此str指向的内容还是NULL,无法对NULL指针进行访问,程序会崩溃
	strcpy(str, "hello world");
	printf(str);
}
int main()
{
	Test();
	return 0;
}

5.2 题目二:

#include<stdio.h>
#include<stdlib.h>
char* GetMemory(void)
{
	//局部变量
	char p[] = "hello world";
	//调用这个函数时为这个函数创建栈帧空间
	//调用之后这个函数的栈帧空间被销毁
	//局部变量也会被销毁,因此返回局部变量的地址会造成野指针
	return p;
}
void Test(void)
{
	char* str = NULL;
	//此时str就是一个野指针,对其进行访问就是非法访问
	str = GetMemory();
	printf(str);
}
int main()
{
	Test();
	return 0;
}

5.3 题目三:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void GetMemory(char** p, int num)
{
	//p是一个二级指针,接收的是一级指针变量str的地址
	//*p就是str
	*p = (char*)malloc(num);
}
void Test(void)
{
	char* str = NULL;
	//传递的是一级指针变量str的地址
	//形参的改变会影响实参
	GetMemory(&str, 100);
	strcpy(str, "hello");
	printf(str);
	//唯一的缺点就是没有进行动态内存释放,导致内存泄漏
}
int main()
{
	Test();
	return 0;
}

5.4 题目四:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void Test(void)
{
	char* str = (char*)malloc(100);
	strcpy(str, "hello");
	//free之后操作系统回收内存空间,但未将str置空,此时str就是野指针
	free(str);
	if (str != NULL)
	{
		//非法访问
		strcpy(str, "world");
		printf(str);
	}
}
int main()
{
	Test();
	return 0;
}

6 柔性数组

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

typedef struct st_type
{
	int i;
	int arr[];//柔性数组成员
}type_a;

6.1 柔性数组的特点

. 结构体中柔性数组成员前面必须至少有一个其他成员

. sizeof 计算结构体大小是不包括柔性数组大小的

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

#include<stdio.h>
#include<stdlib.h>
typedef struct st_type
{
	//0~3
	int i;//4   8   4
	int arr[];//柔性数组成员
}type_a;
int main()
{
	printf("%zd\n", sizeof(struct st_type));//4
	return 0;
}

6.2 柔性数组的使用

#include<stdio.h>
#include<stdlib.h>
typedef struct st_type
{
	int i;
	int arr[];//柔性数组成员
}type_a;
int main()
{
	//100*sizeof(int)是为了适应柔性数组成员的大小
	type_a* p = (type_a*)malloc(sizeof(type_a) + 100 * sizeof(int));
	if (NULL == p)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	p->i = 100;
	int i = 0;
	for (i = 0; i < 100; i++)
	{
		p->arr[i] = i;
	}
	for (i = 0; i < 100; i++)
	{
		printf("%d ", p->arr[i]);
	}
	free(p);
	p = NULL;
	return 0;
}

柔性数组的使用还可以这样完成。

#include<stdio.h>
#include<stdlib.h>
typedef struct st_type
{
	int i;
	int *a;//柔性数组成员
}type_a;
int main()
{
	type_a* p = (type_a*)malloc(sizeof(type_a));
	if (NULL == p)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	p->i = 100;
	p->a = (int*)malloc(p->i * sizeof(int));
	if (p->a == NULL)
	{
		printf("p->a malloc fail\n");
		exit(-1);
	}
	int i = 0;
	for (i = 0; i < 100; i++)
	{
		p->a[i] = i;
	}
	for (i = 0; i < 100; i++)
	{
		printf("%d ", p->a[i]);
	}
	free(p->a);
	p->a = NULL;
	free(p);
	p = NULL;
	return 0;
}

比较两种方式,哪一种更好呢?第一种方法会更好一点。

1.方便内存释放

2.有利于访问速度连续的空间有利于提高访问速度,也有利于减少内存碎片)。

7 C/C++中程序内存区域划分

在这里插入图片描述

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

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

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

相关文章

《算法岗面试宝典》重磅发布!

大家好&#xff0c;历时半年完善&#xff0c;《算法岗面试宝典》 终于可以跟大家见面了。 最近 ChatGPT 爆火&#xff0c;推动了技术圈对大模型算法场景落地的热情&#xff0c;就业市场招聘人数越来越多&#xff0c;算法岗一跃成为竞争难度第一的岗位。 岗位方向 从细分方向…

李宏毅深度学习-梯度下降和Normalization归一化

Gradient Descent梯度下降 ▽ -> 梯度gradient -> vector向量 -> 下图中的红色箭头&#xff08;loss等高线的法线方向&#xff09; Tip1: Tuning your learning rates Adaptive Learning Rates自适应 通常lr会越来越小 Adaptive Learning Rates中每个参数都给它不同…

110.WEB渗透测试-信息收集-ARL(1)

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a; 易锦网校会员专享课 上一个内容&#xff1a;109.WEB渗透测试-信息收集-FOFA语法&#xff08;9&#xff09; 信息收集自动化工具-灯塔…

黑马头条day6-kafka及异步通知文章上下架

今天任务比较水 主要是kafka入门和 文章上下架 以及异步通知article同步到app的前端数据 需要重新看一下&#xff08;使用步骤并不是很复杂 kafka主要解决高并发&#xff09; 1 kafka的入门 和 使用异步 需要重新看一下了流程和 详细信息 2 bug 打开app页面的时候出现503 服…

从0到1深入浅出构建Nest.Js项目

Nest (NestJS) 是一个用于构建高效、可扩展的 Node.js 服务器端应用程序的开发框架。它利用JavaScript 的渐进增强的能力&#xff0c;使用并完全支持 TypeScript &#xff08;仍然允许开发者使用纯 JavaScript 进行开发&#xff09;&#xff0c;并结合了 OOP &#xff08;面向对…

动手学运动规划: 2.2.c 3次样条曲线代码解析

学习? 学个P!☺ — 亮剑 李云龙 &#x1f3f0;代码及环境配置&#xff1a;请参考 环境配置和代码运行! 本节提供了3次样条曲线的代码测试 python3 tests/curves/cubic_spline.py2.2.c.1 3次样条曲线代码实现 CubicSpline1D实现了1维的3次样条曲线, 需要输入一组离散点. Cub…

现在别买理想L7/L8,问界M8要来暴揍友商了

文 | AUTO芯球 作者 | 雷慢 问界又一重磅炸弹要来了&#xff0c; 它就是问界M8&#xff0c; 来看&#xff0c;M8刚又曝光了大量谍照。 现在我打听的消息是这样的&#xff0c; 11月广州车展亮相预售&#xff0c; 12月底正式上市&#xff0c;25年春节前后开始交付&#xff…

计算机网络:计算机网络体系结构 —— 专用术语总结

文章目录 专用术语实体协议服务服务访问点 SAP 服务原语 SP 协议数据单元 PDU服务数据单元 SDU 专用术语 实体 实体是指任何可以发送或接收信息的硬件或软件进程 对等实体是指通信双方处于相同层次中的实体&#xff0c;如通信双方应用层的浏览器进程和 Web 服务器进程。 协…

Java组件化开发:jar包

我在java基础&#xff1a;原始数据类型&#xff0c;包的创建与导入-CSDN博客一文中记录了包的使用&#xff0c;此文就详细讲解一下IDEA中如何进行组件化开发。 介绍 现在的软件系统功能越来越复杂&#xff0c;规模也越来越大&#xff0c;为了应对这种挑战&#xff0c;人们将“…

深入解析Python错误消息及解决方法

深入解析Python错误消息及解决方法 Python是开发者广泛使用的语言&#xff0c;因其简洁的语法和强大的标准库而深受欢迎。然而&#xff0c;Python程序在运行过程中&#xff0c;错误不可避免。理解Python的错误消息并正确处理这些错误&#xff0c;是提升代码质量和调试效率的重…

3.点位管理改造-列表查询——帝可得管理系统

目录 前言一、与页面原型差距1.现在&#xff1a;2.目标&#xff1a;3. 存在问题&#xff1a;所在区域和合作商ID展示的都是ID&#xff0c;而不是名称&#xff1b;同时合作商ID应改为合作商 二、修改1.重新设计SQL语句2.修改mapper层&#xff0c;使用Mybatis中的嵌套查询3.修改s…

AI人工智能人像修饰中文面板PS插件 Retouch Pro 3.2.0 中文汉化版

AI人工智能人像修饰PS扩展插件 Retouch Pro 3.2.0 中文汉化版 支持软件&#xff1a;PS 2018 – PS 2025或更高版本 系统要求&#xff1a;Windows系统 或 MacOS系统 出处&#xff1a;https://www.aeown.com/thread-3061-1-1.html Retouch Pro Panel 有一个非常强大和先进的人工…

Python Tips6 基于数据库和钉钉机器人的通知

说明 起因是我第一版quant程序的短信通知失效了。最初认为短信是比较即时且比较醒目的通知方式&#xff0c;现在看来完全不行。 列举三个主要问题&#xff1a; 1 延时。在早先还能收到消息的时候&#xff0c;迟滞就很严重&#xff0c;几分钟都算短的。2 完全丢失。我手机没有…

ACP科普:SoSM和CPO

在Scrum of Scrums&#xff08;SoS&#xff09;框架中&#xff0c;SoSM&#xff08;Scrum of Scrums Master&#xff09;和CPO&#xff08;Chief Product Owner&#xff09;是两个关键角色&#xff0c;负责协调多个Scrum团队的工作&#xff0c;确保项目的顺利进行。以下是对这两…

Android AMS介绍

注&#xff1a;本文为作者学习笔记&#xff0c;如有误&#xff0c;请各位大佬指点 系统进程运行环境的初始化 Context是一个抽象类&#xff0c;它可以访问application环境的全局信息和各种资源信息和类 context功能&#xff1a; 对Activity、Service生命周期的管理通过Intent发…

c++进阶之多态讲解

这篇文章和大家一起学习一下c中的多态 多态的概念 多态的概念&#xff1a;通俗来讲&#xff0c;就是多种形态。多态分为编译时多态(静态多态)和运⾏时多态(动态多态)。 什么是静态多态 前⾯讲的函数重载和函数模板&#xff0c;它们传不同类型的参数就可以调用不同的函数&…

深入理解 C 语言中的内存操作函数:memcpy、memmove、memset 和 memcmp

目录&#xff1a; 前言一、 memcpy 函数二、 memmove 函数三、 memset 函数四、 memcmp 函数总结 前言 在 C 语言中&#xff0c;内存操作函数是非常重要的工具&#xff0c;它们允许我们对内存进行直接操作&#xff0c;从而实现高效的数据处理。本文将深入探讨四个常用的内存操…

zabbix7.0web页面删除主机操作实现过程

前言 服务端配置 链接: rocky9.2部署zabbix服务端的详细过程 被监控端配置 链接: zabbix7.0监控linux主机案例详解 环境 主机ip应用zabbix-server192.168.10.11zabbix本体zabbix-client192.168.10.12zabbix-agent zabbix-server(服务端已配置) zabbix-client(被监控端已配置…

Bruno:拥有 11.2k star 的免费开源 API 测试工具

Github 开源地址&#xff1a; https://github.com/usebruno/bruno 官网地址&#xff1a; https://www.usebruno.com/ 下载地址&#xff1a; https://www.usebruno.com/downloads 使用文档&#xff1a; https://docs.usebruno.com/ Bruno 是一款全新且创新的 API 客户端&…

微调学习记录

目前看到的市面上的微调文章&#xff0c;想大体上给他们分个类&#xff0c;方便后续进行重点学习 参数高效微调 1. LoRA 不用多说含金量 2. Rein https://github.com/w1oves/rein 把它也算进来了&#xff0c;类似。 Adapter adapter类的我感觉都大差不差&#xff0c;具体…