动态内存管理(一)

简介: 动态内存管理

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


首先,常规的内存开辟就是创建变量或者数组


int i = 0;//在栈空间上开辟4个字节
int arr[10] = { 0 };//在栈空间上开辟40个字节


这两种开辟空间的方式有两个特点:


1. 空间开辟大小是固定的(不可再次修改)
 2. 数组在声明时,必须给定数组的大小,开辟的内存在编译时进行分配


这两种开辟内存的方式太过简单,有时不一定能满足自己编程的需求,需要更加高级的内存开辟方式,便可以试试动态内存开辟的方式


2.动态内存函数


C语言提供一定数目的动态内存开辟的函数


2.1malloc


b908de3d30bc41294ade6a73fbfb4f5c_9aa43c5cb1dc40d090ceb404d2b98d2c.png

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


1. 如果开辟成功,则返回一个指向这块空间的指针
 2. 如果开辟失败,则返回一个NULL指针,所有 malloc 的返回值必须
    要进行检查
 3.  malloc函数并不知道所开辟空间的类型,返回值的类型是 void*
      由使用者在使用过程决定
 5. 如果参数 size 为 0,malloc的执行结果是未定义的,取决于编译器


2.2free


既然有开辟内存的函数,那就一定存在释放内存的函数。


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


e896ccf70ad9e91492606896358ac85d_0d00e6a4783c4ef4b38b9ab3b9725d00.png


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


1. 如果参数 ptr 指向的空间不是动态开辟的,则 free 函数的执行
    结果是未定义的,结果取决于编译器
 2. 如果参数 ptr 指向的是 NULL 指针,则函数不会执行任何操作


#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
int main()
{
  //动态内存开辟
  int* p = (int*)malloc(40);
  //对malloc返回值进行检查,若是NULL则打印错误原因
  if (p == NULL)
  {
  printf("%s\n", strerror(errno));
  return;
  }
  //使用
  //初始化
  int i = 0;
  for (i = 0; i < 10; i++)
  {
  *(p + i) = i;
  }
  //打印
  for (i = 0; i < 10; i++)
  {
  printf("%d ", *(p + i));
  }
  //释放空间
  free(p);
  //将指针设置成空指针,避免造成野指针
  p=NULL;
  return 0;
}


如果在程序结尾没有加上free

并不是表示内存空间不进行回收,而是当程序退出时,系统会自动回收所开辟的空间,当然最保险的方式还是加上free函数


7836ebaae6a4c1b557f2a9070ff3baa0_fac35c4297fe4fafaf9df8e12d14c470.png


图形展示如下


b8129690255fe5d5dcfe65ba5666143e_711b8c8a362a4d0da789943f6fca5054.png


2.3calloc


f19506cf47fa43f729c3f0a80d114a57_791bf6eb4a22417283a3d94b705d86ae.png

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


1. 函数的功能是开辟num个大小为size的的元素的空间,并将空间的
    每个字节都初始化为0
 2. calloc==malloc+memset


例如


#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
int main()
{
  //动态内存开辟
  int* p = (int*)calloc(10, sizeof(int));
  //对calloc函数的返回值进行检查,若是NULL则打印错误原因
  if (p == NULL)
  {
  printf("%s\n", strerror(errno));
  return;
  }
  //使用
  //打印
  int i = 0;
  for (i = 0; i < 10; i++)
  {
  printf("%d ", *(p + i));
  }
  //释放空间
  free(p);
  //将指针设置成空指针,避免造成野指针
  p=NULL;
  return 0;
}


初始化结果


33541894359dbd3ebee88d0a8d751900_10209985617e41388cb57f62e1596bc2.png


打印结果


d5e9b259c60a9bf1571d1dab8549a648_e96333bea23c4bb0874633db812358c0.png


图形展示如下


a4a14e12e5bed71f58cd160e957e57c3_3c05731cbf5c463f8a336f7df234584f.png


如果对于申请的内存空间的内容要求初始化,那么使用 calloc函数更加方便


2.4realloc


3a2d6e673899b51cc42a1a6cdcefb27b_bc9bc2d3827046958050666f581f0b74.png


realloc函数可以使动态内存管理更加灵活变通,在编写代码时,总会出现申请的空间太小,或者申请的空间太大,为了更加方便使用,就需要对内存进行灵活的调整。


1. ptr 是待调整的内存地址
 2. size 是调整之后的大小
 3. 返回值是调整之后的内存起始位置


int main()
{
    //动态内存开辟
  int* p = (int*)calloc(10, sizeof(int));
  //对calloc函数的返回值进行检查,若是NULL则打印错误原因
  if (p == NULL)
  {
  printf("%s\n", strerror(errno));
  return;
  }
  int i = 0;
  for (i = 0; i < 10; i++)
  {
  *(p + i) = i;
  }
  //扩展容量
  int* ptr = NULL;
  ptr = (int*)realloc(p, 80);
  if (ptr != NULL)
  {
  p = ptr;
  }
  free(p);
  p = NULL;
  return 0;
}


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

情况1.原有空间后面由足够大的空间

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


659016e3a0671809e8fa75f021aacfe4_4cd297df5c4346b3ab9da454b9fadb03.png


情况2.原有空间之后没有足够大的空间


如果需要扩展内存,原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另外找一个合适大小的连续空间来使用,此时函数返回的便是一个新的内存地址。


525cece3d6ec918e271028a219b1072e_f45519a987354229bed2f9973f69f98b.png


3.常见的动态内存错误


3.1 对NULL指针的解引用操作


int main()
{
  int* p = (int*)malloc(20);
  *p = 20;
  free(p);
  p=NULL;
  return 0;
}

这里的 p 有可能是空指针(NULL),直接解引用非常危险


对此可进行如下修改


int main()
{
  int* p = (int*)malloc(20);
  if (p == NULL)
  {
  return;
  }
  *p = 20;
  free(p);
  p = NULL;
  return 0;
}


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


int main()
{
  int* p = (int*)malloc(20);
  if (p == NULL)
  {
  printf("%s\n", strerror(errno));
  return;
  }
  //访问
  int i = 0;
  //这里就出现数组越界的情况
  for (i = 0; i <= 5; i++)
  {
  *(p + i) = i;
  }
  free(p);
  p = NULL;
  return 0;
}


4308d3aa2ef8f45b7526f212ac4aac00_c45b6bfe5fa64b9f8834b1b9a034deab.png


运行结果如下


53a416e4662a31c35d1c8b1aeb44095c_bf96331e358c4b1ebc9364494962398f.png


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

int main()
{
  int i = 0;
  int* p = &i;
  //.....
  //.....
  free(p);
  p = NULL;
  return 0;
}


free只能释放动态开辟的内存,所以肯定会出现错误的。

运行如下


cb4a002b630f2f7126e66bb7dd459599_7d3875372b3241f8a5b15922fe6da1b4.png


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


int main()
{
  int* p = (int*)malloc(20);
  if (p == NULL)
  {
  return;
  }
  //使用
  int i = 0;
  for (i = 0; i < 5; i++)
  {
  *p = i;
  p++;
  }
  //释放
  free(p);
  p = NULL;
  return 0;
}


因为p不再指向动态内存的起始位置,所以肯定会出现错误


d1dfc80c6adcafbbcf056c171ee01f8d_fe109bb0a86a43799aedbbe64b0cad88.png


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


int main()
{
  int* p = (int*)malloc(20);
  //......
  free(p);
  //.....
  free(p);
  return 0;
}


重复释放也会出现错误


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


void test()
{
  int* p = (int*)malloc(20);
  //.....
  int i = 0;
  scanf("%d", &i);
  if (i == 1)
  {
  return;
  }
  free(p);
  p = NULL;
}
int main()
{
  test();
  return 0;
}


如果 i==1,程序会直接跳出 test函数,从而造成动态开辟的内存没有得到释放,电脑本身也找不到这块内存在何处,最终造成内存泄漏。


目录
相关文章
|
编译器
【动态内存管理】
【动态内存管理】
64 0
|
2月前
|
程序员
动态内存管理
动态内存管理
16 0
|
6月前
|
编译器 C语言
动态内存管理(1)
动态内存管理(1)
49 4
|
6月前
|
程序员 C语言 C++
动态内存管理(2)
动态内存管理(2)
43 1
|
6月前
|
程序员
21.动态内存管理
21.动态内存管理
|
6月前
|
存储 Linux C语言
5.C++动态内存管理(超全)
5.C++动态内存管理(超全)
|
7月前
|
编译器 程序员 C语言
动态内存管理(超详细!)
动态内存管理(超详细!)
62 2
|
7月前
|
安全 C++ 开发者
c++动态内存管理(二)
c++动态内存管理(二)
141 0
|
7月前
|
程序员 C语言 C++
详解动态内存管理!
详解动态内存管理!