C动态内存管理

前言:不知不觉又过去了很长的一段时间。今天对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~3int i;//4   8   4int arr[];//柔性数组成员
}type_a;
int main()
{printf("%zd\n", sizeof(struct st_type));//4return 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.xdnf.cn/news/1553162.html

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

相关文章

【光伏混合储能】VSG并网运行,构网型变流器,虚拟同步机仿真

摘要 本文提出了一种基于光伏发电与混合储能系统结合的虚拟同步发电机&#xff08;VSG&#xff09;控制策略&#xff0c;该策略能够在并网运行时稳定电网电压和频率。通过仿真分析&#xff0c;验证了该策略在各种运行工况下的有效性&#xff0c;展示了其在电力系统中的广泛应用…

了解芯片光刻与OPC

欢迎关注更多精彩 关注我&#xff0c;学习常用算法与数据结构&#xff0c;一题多解&#xff0c;降维打击。 参考资料&#xff1a; 光刻技术与基本流程 https://www.bilibili.com/video/BV1tP4y1j7BA OPC https://www.bilibili.com/video/BV1o94y1U7Td 论文&#xff1a;计算…

CyberBattleSim项目熟悉遇到的问题

在看手册的时候&#xff0c;手册中说需要显卡&#xff0c;配置还不低。 ——师兄说不需要这个显卡&#xff0c;他的独显也能跑&#xff0c;现在能安装配置了&#xff0c;配置文件安装不了确定是否进入了创建的conda环境&#xff0c;多尝试几次。 随着在安装gym的时候&#xf…

【Python报错已解决】TypeError: ‘NoneType‘ object is not callable

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 专栏介绍 在软件开发和日常使用中&#xff0c;BUG是不可避免的。本专栏致力于为广大开发者和技术爱好者提供一个关于BUG解决的经…

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

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

主存储器——随机存取存储器RAM

静态RAM 双稳态触发器 一、工作特性 两种稳定状态&#xff1a; 双稳态触发器具有两个稳定的输出状态&#xff0c;通常表示为 0 和 1&#xff08;或低电平和高电平&#xff09;。这两个状态可以长期保持&#xff0c;即使在没有输入信号的情况下&#xff0c;也不会自发地改变。 例…

Study-Oracle-10-ORALCE19C-RAC集群搭建

一路走来,所有遇到的人,帮助过我的、伤害过我的都是朋友,没有一个是敌人。 ORACLE --RAC 搭建理念:准备工作要仔细,每个参数及配置都到仔细核对。环境准备完成后,剩下的就是图像化操作,没啥难度,所以图形化操作偷懒不续写了。 一、硬件信息及配套软件 1、硬件设置 RA…

C++初阶:STL详解(十)——priority_queue的介绍,使用以及模拟实现

✨✨小新课堂开课了&#xff0c;欢迎欢迎~✨✨ &#x1f388;&#x1f388;养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 所属专栏&#xff1a;C&#xff1a;由浅入深篇 小新的主页&#xff1a;编程版小新-CSDN博客 一.priority_queue的介绍 优先级队列被实现…

手把手教你激活水果音乐制作软件FLStudio Producer Edition 24.1.1.4285 All Plugins汉化中文专业版下载

软件介绍 lmage-Line FL Studio 是由 lmage-Line 公司所开发的一款音乐制作软件&#xff0c;又名:水果音乐。你可以使用FL Studio 软件进行编写&#xff0c;编辑&#xff0c;录制&#xff0c;编辑以及混音和母带制作音乐&#xff0c;目前是世界上最受欢迎的音乐制作工具之一。…

【Linux】Shell脚本基础+条件判断与循环控制

目录 一、介绍 1. Linux提供的Shell解析器 2. bash和sh关系 3. Centos默认的Shell解析器是bash 二、定义 1. 变量名的定义规则 2. 等号周围没有空格 3. 查看变量 4. 删除变量 5. 正确地定义数组 6. 将局部环境变量提升为全局 7. 正确选择引号 8. 特殊变量名 三…

python 开发中识别和解决内存泄漏的技巧

Python 的内存管理是非常优秀的&#xff0c;它使用了自动垃圾回收机制。然而&#xff0c;在某些情况下&#xff0c;内存泄漏依然可能发生。这通常是在复杂的对象引用和循环引用的情境下容易出现&#xff0c;特别是涉及全局变量或不当的引用管理时。内存泄漏问题虽然并不常见&am…

Linux线程(二)线程ID及创建线程详解

1.线程ID 就像每个进程都有一个进程 ID 一样&#xff0c;每个线程也有其对应的标识&#xff0c;称为线程 ID。进程 ID 在整个系统中是唯一的&#xff0c;但线程 ID 不同&#xff0c;线程 ID 只有在它所属的进程上下文中才有意义。 进程 ID 使用 pid_t 数据类型来表示&#xf…

记录cocoscreater3.8.x设置2d卡牌圆角

引擎版本&#xff1a;Cocos Creater3.8.3版本 1.在Card节点上添加Mask组件&#xff0c;类型选择 2.在Card节点上绑定CardController.ts脚本 3.在CardController.ts编写圆角脚本&#xff0c;其实就是动态绘制Graphics组件 import { _decorator, Color, Component, Graphics, …

排序01 多目标模型

引入 使用机器学习方法对指标做预估&#xff0c;再对预估分数做融合。融合方法&#xff1a;加权和方法给不同指标赋予不同的权重&#xff0c;权重是做A/B test调试得到的。还有更好地融合方法。 多目标模型 排序模型的输入是各种各样的特征&#xff0c;用户特征主要是用户id和…

ADRC与INDI的关系

ADRC与INDI的关系 前言 一直热衷于把一些基础的东西想明白&#xff0c;这样才能更好地理解一些稍微复杂些的算法&#xff0c;在深入理解这些算法后才能更好地应用。 例如 用回路成型方法探究ADRC各参数对闭环系统的影响对比KF和RLS的关系互补滤波的原理以及参数整定&#xf…

【Python报错已解决】TypeError: not enough arguments for format string

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 专栏介绍 在软件开发和日常使用中&#xff0c;BUG是不可避免的。本专栏致力于为广大开发者和技术爱好者提供一个关于BUG解决的经…

C++【类和对象】(再探构造函数、类型转换与static成员)

文章目录 1. 再探构造函数2. 类型转换3. static成员结语 1. 再探构造函数 之前我们实现构造函数时&#xff0c;初始化成员变量主要使用函数体内赋值&#xff0c;构造函数初始化还有⼀种方式&#xff0c;就是初始化列表&#xff0c;初始化列表的使用方式是以⼀个冒号开始&#…

体系结构论文(五十三):Featherweight Soft Error Resilience for GPUs 【22‘ MIRCO】

Featherweight Soft Error Resilience for GPUs 一、文章介绍 背景&#xff1a;软错误通常由高能粒子&#xff08;如宇宙射线和α粒子&#xff09;打击电路造成的位翻转&#xff0c;可能导致程序崩溃或产生错误输出。随着电子技术的进步&#xff0c;电路对这种辐射引发的软错…

电子连接器温升仿真教程 二

在《电子连接器温升仿真教程 一》中详细介绍了用内热法做电子连接器温升仿真的操作步骤与方法,本教程将讲解用电流电压法做电子连接器温升仿真。 本教程,将以下面产品为例演示温升仿真方法其操作步骤。 该连接器为电池连接器,其Housing材料为LCP+30%GF,端子材质为铍铜…