动态内存管理(超详细!)

简介: 动态内存管理(超详细!)

为什么要有动态内存管理呢

大家在此前的C语言学习中已经知道,我们要定义一个值,首先要为它在内存空间上开辟一个空间,通常情况下我们用这种方式来开辟:

int val = 20;//在内存空间上开辟四个字节
char arr[10] = {0};//在内存空间上开辟10个字节的空间

但是,以上的开辟空间的方式有以下的特点:

1. 空间开辟⼤⼩是固定的。
2. 数组在申明的时候,必须指定数组的⻓度,数组空间⼀旦确定了⼤⼩不能调整

但是通常情况先我们对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间⼤⼩在程序运⾏的时候才能知道,那数组的编译时开辟空间的⽅式就不能满⾜了。

因此,C语⾔引⼊了动态内存开辟,让程序员⾃⼰可以申请和释放空间,就可以使得内存空间变得灵活,同时也可以不浪费空间。

动态内存函数

动态内存函数有以下四个:

malloc
free
calloc
realloc

下面我们将四个函数进行详解:

malloc

malloc的函数原型如下

void* malloc (size_t size)

malloc函数向内存申请⼀块连续可⽤的空间,并返回指向这块空间的指针,并且不知道所开辟空间是什么类型,可能是char,可能是int,所以返回值类型为void*

下面有几个需要注意的点:

1.malloc函数并不是会一直成功的,也就是说,可能会发生malloc函数申请内存空间失败的情况,那么malloc函数就会返回一个空指针,所以我们在使用malloc函数申请空间后,可以使用if语句进行判断是否申请空间成功

例如:我们用malloc函数开辟20个字节的整形空间

这里我们用到了perror函数,可以打印函数的错误信息

int* ptr = malloc(20);
  if (ptr == NULL)
  {
    perror("malloc");
    return;//如果开辟失败,程序结束
  }

2.如果参数 size 为0,由于malloc函数的⾏为是标准是未定义的,而是取决于编译器,所以,在不同的编译器的会出现不同的情况,部分的编译器可以开辟0个字节的空间

free

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

void free (void* ptr)

free函数有一个特别需要注意的点:

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

注:在free(ptr)后,我们还可以将其置为空指针,防止ptr变为野指针

什么意思呢?我们举一个例子:

int main()
{
  int a = 1;
  int* ptr = &a;
  free(ptr);
  ptr=NULL;
  return 0;
}

这个代码在运行是就会中断,因为ptr所指向的空间不是动态开辟的

可能有些同学会问,那如果ptr指向的空间为空指针呢,代码跑起来会有错误吗?

例如:

int* ptr=NULL;
  free(ptr);

答案是不会!如果参数是NULL指针,则函数什么事都不做!

大家还要注意,free和malloc函数的声明都在 stdlib.h 头⽂件中

calloc

calloc 函数也⽤来动态内存分配。原型如下:

void* calloc (size_t num, size_t size)

calloc函数就是为num个大小为size字节的元素开辟一块空间。

大家可能会有疑问,calloc函数和malloc函数没有区别啊?

其实不然,calloc函数开辟空间之后还会将num个元素全部初始化为0!

我们用代码验证一下:

用calloc函数开辟十个整形大小的空间,并将其打印

#include <stdio.h>
#include <stdlib.h>
int main()
{
  int* p = (int*)calloc(10, sizeof(int));
  if (NULL != p)
  {
    int i = 0;
    for (i = 0; i < 10; i++)
    {
      printf("%d ", *(p + i));
    }
  }
  free(p);
  p = NULL;
  return 0;
}

打印结果如下:

所以, calloc和malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0

realloc

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

函数原型如下:

void* realloc (void* ptr, size_t size)

ptr 是要调整的内存地址, size 是调整内存之后内存空间的新⼤⼩,返回值为调整之后的内存起始位置

调整原内存空间⼤⼩的基础上,还会将原内存中的数据移动到新的空间

记住,是新的空间!

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

情况1:原有空间之后有⾜够⼤的空间

调整方式:

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

情况2:原有空间之后没有⾜够⼤的空间

调整方式:

在堆空间上另找⼀个合适⼤⼩的连续空间使⽤,函数返回的是新的内存地址

常⻅的动态内存的错误

讲解完动态内存函数后,我们在日常的代码中可能会出现一些常见的错误,下面我们举几个具体的例子

对NULL指针的解引⽤操作
int *p = (int *)malloc(INT_MAX/4);
 *p = 20;
 free(p);

如果我们开辟空间失败后返回了空指针,但是我们后续又对p进行了解引用操作,这样就会出现错误

对动态开辟空间的越界访问
int i = 0;
 int *p = (int *)malloc(10*sizeof(int));
 if(NULL == p)
 {
 exit(EXIT_FAILURE);
 }
 for(i=0; i<=10; i++)
 {
 *(p+i) = i;
 }
 free(p);

我们只为p申请了十个int的空间,而此代码中的for循环却访问了下标为10的元素,出现了越界访问

对⾮动态开辟内存使⽤free释放
int a = 1;
  int* ptr = &a;
  free(ptr);
  ptr=NULL;
  return 0;

我们的p指向的a并不是动态开辟的内存,所以free就是未定义的

使⽤free释放⼀块动态开辟内存的⼀部分
int *p = (int *)malloc(100);
 p++;
 free(p)

这里的p已经进行“++”操作,p不再指向内存起始的位置,并没有释放整个动态内存,而是一部分

对同⼀块动态内存多次释放
int *p = (int *)malloc(100);
 free(p);
 free(p);

此代码中连续进行了两次free,会出现bug

但是如果是下面这种情况就无妨了:

因为将p置为空指针了,后续的free也就不起作用了

int *p = (int *)malloc(100);
 free(p);
 p=NULL;
 free(p);
动态开辟内存忘记释放(内存泄漏)
int *p = (int *)malloc(100);
 if(NULL != p)
 {
 *p = 20;
 }

此代码没有对p进行内存的释放,我们申请了一百个字节的整形空间大小,并没有使用怎么多的空间,虽然操作系统会自动回收内存,但是会产生较多的内部碎片,效率不如free,仍然会有较大的浪费,就产生了内存泄漏

好了,以上就是今天的分享了,谢谢大家!

相关文章
|
5月前
|
编译器 C语言
动态内存管理(1)
动态内存管理(1)
41 4
|
5月前
|
程序员 C语言 C++
动态内存管理(2)
动态内存管理(2)
38 1
|
C语言 Python
动态内存管理(下)
动态内存管理(下)
57 0
|
5月前
|
程序员
21.动态内存管理
21.动态内存管理
|
6月前
|
安全 C++ 开发者
c++动态内存管理(二)
c++动态内存管理(二)
139 0
|
6月前
|
程序员 C语言 C++
详解动态内存管理!
详解动态内存管理!
|
11月前
|
程序员 编译器
动态内存管理-1
动态内存管理
53 0
|
C语言 C++
C++中的动态内存管理
C++中的动态内存管理
|
编译器 文件存储 数据库
Day_17> 动态内存管理
Day_17> 动态内存管理
|
C语言
动态内存管理(上)
动态内存管理(上)
46 0