动态内存管理

简介: 动态内存管理

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

计算机的内存,粗略得可以分为栈区、堆区、静态区

220541c901a44502b9466bc224a7b3c3.png


我们之前学到的内存开辟是定义一个变量或定义一个数组


int num = 10;
int arr[10] = {0};


上述都是在栈区上开辟的空间,这样开辟的空间有两个特点:

1.空间开辟大小是固定的

2.数组在定义时,在VS环境中C99的规定下,必须指定数组长度,并且数组长度必须是常量,不可以是变量


对于空间的需求,如果我们知道要开辟空间的大小,那么可以用上述的开辟方式

但是有时候我们需要的空间大小在程序运行的时候才能知道,那数组的编译时开辟空间的方式就不能满足了,这就需要用动态内存开辟了


2.动态内存函数

C语言中,有一些动态开辟的库函数,这些函数都声明在stdlib.h头文件中

并且这些函数是在内存中的堆区开辟的空间


malloc
void* malloc (size_t size);
1

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

如果开辟失败,返回一个NULL指针,所以在malloc后要检查返回值

因为函数不知道开辟的空间是什么类型,所以在函数设计时,就设计返回一个void*指针

返回值类型是void*,在使用时由使用者决定,所以要把返回的指针进行强制类型转化成其他类型的指针

如果参数size为0,malloc的行为是C语言中未规定的,取决于编译器

下面我们开辟一个存放int类型的空间


#include <stdio.h>
#include <stdlib.h>
#include<string.h>
#include<errno.h>
int main()
{
  int* arr = NULL;
  arr = (int*)malloc(40);
  //检测是否malloc成功
  if (arr == NULL)
  {
  strerror(errno);
  return 1;
  }
}


开辟出的是连续的空间,所以与数组类似,我们可以通过下标访问空间

#include <stdio.h>
#include <stdlib.h>
#include<string.h>
#include<errno.h>
int main()
{
  int* arr = NULL;
  arr = (int*)malloc(40);
  //检测是否malloc成功
  if (arr == NULL)
  {
  strerror(errno);
  return 1;
  }
  int i = 0;
  for(i=0; i<num; i++)
  {
  *(arr+i) = 0;
  //arr[i] = 0;通过下标访问
  }
}



free

C语言中提供了另一个free函数,专门是用来动态内存的释放和回收


void free (void* ptr);

1

free函数是专门用来释放动态开辟的内存,ptr指向动态开辟的空间。

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

如果参数 ptr是NULL指针,则函数什么事都不做。

free掉空间后还要把ptr置为空


int main()
{
  int* arr = NULL;
  arr = (int*)malloc(40);
  //检测是否malloc成功
  if (arr == NULL)
  {
  strerror(errno);
  return 1;
  }
  int i = 0;
  for(i=0; i<num; i++)
  {
  *(arr+i) = 0;
  //arr[i] = 0;通过下标访问
  }
  free(arr);//释放ptr所指向的动态内存
  arr = NULL;
  return 0;
}



calloc

函数原型:


void* calloc (size_t num, size_t size);

1

该函数的功能是开辟num个大小为size的元素开辟一块空间

该数会把开辟出的空间每个字节初始化为0

其他与malloc用法相同

int main()
{
  int *p = (int*)calloc(10,sizeof(int));
  //检测是否calloc成功
  if (arr == NULL)
  {
  strerror(errno);
  return 1;
  }
  int i = 0;
  for(i=0; i<num; i++)
  {
  *(arr+i) = 0;
  //arr[i] = 0;通过下标访问
  }
  free(arr);//释放ptr所指向的动态内存
  arr = NULL;
  return 0;
}




realloc

realloc函数使动态内存管理更加灵活


有时我们发现之前申请的动态空间太小了,或者太大了,为了得到合理大小的内存,我们就要用到realloc函数对内存大小进行


函数原型:


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

2c1de7662c3a439c8833a9450c581455.png2c1de7662c3a439c8833a9450c581455.png1

ptr是要调整的内存地址,size是调整后的大小

返回值是调整之后的内存起始位置

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

realloc开辟出的新空间不会初始化

要用一个新的指针去接收realloc调整后的地址,因为如果用旧的指针去维护它,如果扩容失败,返回NULL,不但扩容失败了,原空间中的数据也丢失了

realloc调整内存空间有2种情况:


情况一:(原地扩容)原有空间后有足够大的空间进行扩容


要扩展内存就直接在原有内存之后直接追加空间,原来空间的数据不发生变化

2c1de7662c3a439c8833a9450c581455.png


情况2:(异地扩容)原有空间之后没有足够大的空间进行扩容


在堆空间上另找一个合适大小的连续空间来使用。这样函数返回的是一个新的内存地址。

原内存空间中的内容也会拷贝到新的空间上



c0b91a8b6ca54c7b8434054f1f53d11a.png


异地扩容后,原内存空间会被自动释放掉


int main()
{
  int* p = (int*)malloc(5 * sizeof(int));
  if (p == NULL)
  {
  perror("malloc");
  return 1;
  } 
  int* ptr = (int*)realloc(p, 10 * sizeof(int));
  if (ptr != NULL)
  {
  p = ptr;//realloc成功,就把新地址的值赋给旧地址,还是让旧指针维护这个空间
  }
  //realloc在开辟空间后,不会进行初始化
  free(p);
  p = NULL;
  return 0;
}


3.使用动态内存要注意的几点

对NULL的解引用

void test()
{
  int *p = (int *)malloc(INT_MAX/4);
  *p = 20;//如果p的值是NULL,就会有问题
  free(p);
}


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

void test()
{
  int *p = (int *)malloc(100);
  free(p);
  free(p);//重复释放
}


free非动态开辟的内存

void test()
{
  int a = 10;
  int *p = &a;
  free(p);//不可以free掉动态开辟的内存
}


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

void test()
{
  int *p = (int *)malloc(100);
  p++;
  free(p);//p不再指向动态内存的起始位置
}
1


一个函数中开辟动态空间传到其他函数中,同样需要free

#include <stdio.h>
#include <stdlib.h>
int* getmem()
{
  int* p = (int*)malloc(40);
  return p;
}
int main()
{
  int* arr = getmem();
  free(arr);//需要free掉通过函数传来的动态内存
  arr = NULL;
  return 0;
}


4.例题

例题1

以下代码有什么错误?


void GetMemory(char *p)
{
  p = (char *)malloc(100);
}
void Test(void)
{
  char *str = NULL;
  GetMemory(str);
  strcpy(str, "hello world");
  printf(str);
}
1


11

*1. str传给p,p是str的临时拷贝,有自己独立的空间,在p中进行动态内存开辟,但是str仍为空,strcpy拷贝时,非法访问内存

*



5bbf77b1b8804fdca41949fcda5bd91c.png





2.在GetMemory中开辟了动态内存,但是并没有free释放掉,所以会内存泄漏

想要解决这个问题,改变指针的指向,就要用到二级指针


void GetMemory(char **p)
{
  *p = (char *)malloc(100);
}
void Test(void)
{
  char *str = NULL;
  GetMemory(&str);
  strcpy(str, "hello world");
  printf(str);
}


例题2

char *GetMemory(void)
{
  char p[] = "hello world";
  return p;
}
void Test(void)
{
  char *str = NULL;
  str = GetMemory();
  printf(str);
}


上面的代码也有问题,GetMemory函数中定义了一个字符串,并且返回字符串首字符地址,主函数中用str接收,但是出了GetMemory后,在GetMemory中定义的局部变量会自动销毁,所以str还是一个空指针


像这种问题都叫做返回栈空间地址的问题


目录
相关文章
|
JavaScript 前端开发 安全
深度剖析之由浅入深揭秘JavaScript类型转换(最全总结篇)(三)
深度剖析之由浅入深揭秘JavaScript类型转换(最全总结篇)(三)
183 0
|
网络安全 Windows
SecureCRT乱码问题解决
SecureCRT乱码问题解决
SecureCRT乱码问题解决
|
Prometheus 监控 Cloud Native
我的周刊(第069期)
我的周刊(第069期)
|
SQL 存储 算法
MySQL-索引(上)
MySQL-索引(上)
MySQL-索引(上)
|
前端开发 算法 架构师
同步的 ReactDOM.render,异步的 ReactDOM.createRoot
本文主要讲同步的 ReactDOM.render,异步的 ReactDOM.createRoot
927 0
|
机器学习/深度学习 传感器 机器人
DeepMind提出基于视觉的强化学习模型,十八般兵器对机器人不在话下
人类能够模仿别人的行为,根据给定的目标来规划自己的行动。DeepMind最近发布了一个新模型,赋予机器人相同的能力,最终生成的模型与操作器、任务都无关,具有更好地通用性。
325 0
DeepMind提出基于视觉的强化学习模型,十八般兵器对机器人不在话下
|
9天前
|
存储 关系型数据库 分布式数据库
PostgreSQL 18 发布,快来 PolarDB 尝鲜!
PostgreSQL 18 发布,PolarDB for PostgreSQL 全面兼容。新版本支持异步I/O、UUIDv7、虚拟生成列、逻辑复制增强及OAuth认证,显著提升性能与安全。PolarDB-PG 18 支持存算分离架构,融合海量弹性存储与极致计算性能,搭配丰富插件生态,为企业提供高效、稳定、灵活的云数据库解决方案,助力企业数字化转型如虎添翼!
|
8天前
|
存储 人工智能 Java
AI 超级智能体全栈项目阶段二:Prompt 优化技巧与学术分析 AI 应用开发实现上下文联系多轮对话
本文讲解 Prompt 基本概念与 10 个优化技巧,结合学术分析 AI 应用的需求分析、设计方案,介绍 Spring AI 中 ChatClient 及 Advisors 的使用。
364 130
AI 超级智能体全栈项目阶段二:Prompt 优化技巧与学术分析 AI 应用开发实现上下文联系多轮对话