【C语言】动态内存管理(上)

简介: 【C语言】动态内存管理(上)

前言:

本篇主要围绕动态内存的开辟方法,容易出现的错误比如内存泄露、未检查动态内存函数返回值、free后指针未置空造成野指针,C/C++程序内存区域划分以及柔性数组进行讲解,本篇文章干货满满,感兴趣的读者可以三连加关注🌟🌟🌟

一、为什么存在动态内存分配

我们之前掌握的内存开辟方法有两个特点:

  • 空间开辟大小是固定的。
  • 数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配。

但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道, 那数组的编译时开辟空间的方式就不能满足了。 这时候引入动态内存开辟方法。

二、动态内存函数的介绍

动态内存函数的头文件为stdlib.h。

(一)malloc和free

void* malloc(size_t size);

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

  • 如果开辟成功,则返回一个指向开辟好空间的指针。
  • 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
  • 返回值的类型是void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己 来决定。
  • 如果参数size为0,malloc的行为是标准是未定义的,取决于编译器。
  • malloc并不会初始化这块空间的内容,calloc能初始化。

返回值检查的方式:

int main()
{
  int arr[10];
  int* p = (int*)malloc(40);
  if (p == NULL)
  {
    perror("malloc");//见博主主页字符串(一)
    return 0;
  }
  return 0;
}

free是专门用来做动态内存的释放和回收的。

动态申请的内存空间,当程序不退出时,动态申请的内存不会主动释放,需要free释放。

void free(void* ptr);

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

  • 如果参数ptr指向空间不是动态开辟的,那free函数的行为是未定义的。
  • 如果参数ptr是NULL指针,则函数什么事都不做。

我们来看一看malloc和free的用法:

#include <stdlib.h>
int main()
{
  //代码1
  int num = 0;
  scanf("%d", &num);
  int arr[num] = { 0 };
  //代码2
  int* ptr = NULL;
  ptr = (int*)malloc(num * sizeof(int));
  if (NULL != ptr)//检查malloc的返回值
  {
    int i = 0;
    for (i = 0; i < num; i++)
    {
      *(ptr + i) = 0;
    }
  }
  free(ptr);//释放ptr所指向的动态内存
  ptr = NULL;//是否有必要?
  return 0;
}

我看来看一看代码中提出的问题,是否要将释放后的指针置空,其实这非常重要,因为free后,ptr指向的动态内存就被归还给操作系统了,此时ptr就是一个野指针。

养成好习惯,free后置空。

(二)calloc

void* calloc (size_t num, size_t size);

  • 函数的功能是为num个大小为size的元素开辟一块空间,并且把空间的每个字节初始化为0
  • 与函数malloc的区别只在于calloc会在返回地址之前把申请的空间的每个字节初始化为全0,realloc也不初始化。

(三)realloc

realloc是最应该学习的动态内存开辟函数。

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

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

  • ptr是要调整的内存地址,如果ptr为空指针,则会开辟size大小的空间,此时与malloc功能相同。
  • size为调整之后总的新大小。
  • 返回值为调整之后的内存起始位置。
  • 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间。

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

  1. 当原有空间之后有足够大的空间时:直接在原有内存之后追加空间,原来空间的数据不发生变化。
  2. 当原有空间之后没有足够大的空间:在堆空间上另找一个合适大小的连续空间来使用,这个另外开辟的空间大小为扩容后整个空间的大小,将原来的数据拷贝到新空间,并释放旧空间,返回新空间的起始地址。

我们来看一看realloc是如何使用的,并且文章前言提到的内存泄露是什么:

int main()
{
    int* ptr = (int*)malloc(100);
    if (ptr != NULL)//检查malloc返回值
    {
        //业务处理
    }
    else
    {
        exit(EXIT_FAILURE);
    }
    //扩展容量
    //代码1
    ptr = (int*)realloc(ptr, 1000);//这样可以吗?(如果申请失败会如何?)
    //代码2
    int* p = NULL;
    p = realloc(ptr, 1000);
    if (p != NULL)
    {
        ptr = p;
    }
    //业务处理
    free(ptr);
    ptr = NULL;  //free后置空,养成好习惯
    p = NULL;
    return 0;
}

我们来分析一下这段代码。

首先代码1就会造成内存泄露:

如果申请失败,realloc返回NULL,此时ptr也被赋为空指针,这样一来不仅扩容失败,原来的动态内存空间也找不到了,注意这块找不到的内存空间还未释放,这种情况就叫做内存泄露,即该块动态内存用不上也找不到。

正确方式应为代码2,申请成功的情况下才将p赋给ptr,这样处理可以有效防止内存泄露。

三、常见的动态内存错误

(一)对NULL指针的解引用操作

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

(二)对动态开辟空间的越界访问

void test()
{
  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;//当i是10的时候越界访问
  }
  free(p);
}

(三)对非动态开辟内存使用free释放

void test()
{
  int a = 10;
  int* p = &a;
  free(p);//err
}


目录
相关文章
|
10天前
|
C语言 C++
C语言 之 内存函数
C语言 之 内存函数
25 3
|
11天前
|
编译器 程序员 C语言
深入C语言:动态内存管理魔法
深入C语言:动态内存管理魔法
|
16天前
|
存储 程序员 编译器
C语言——动态内存管理与内存操作函数
C语言——动态内存管理与内存操作函数
|
6天前
|
C语言
保姆级教学 - C语言 之 动态内存管理
保姆级教学 - C语言 之 动态内存管理
11 0
|
11天前
|
存储 C语言
深入C语言内存:数据在内存中的存储
深入C语言内存:数据在内存中的存储
|
11天前
|
C语言 C++
c语言回顾-内存操作函数
c语言回顾-内存操作函数
34 0
|
13天前
|
存储 C语言 C++
来不及哀悼了,接下来上场的是C语言内存函数memcpy,memmove,memset,memcmp
本文详细介绍了C语言中的四个内存操作函数:memcpy用于无重叠复制,memmove处理重叠内存,memset用于填充特定值,memcmp用于内存区域比较。通过实例展示了它们的用法和注意事项。
43 0
|
18天前
|
存储 程序员 C语言
C语言动态内存管理
C语言动态内存管理
|
18天前
|
程序员 C语言
C语言内存函数精讲
C语言内存函数精讲
|
19天前
|
编译器 C语言 C++
【C语言】精妙运用内存函数:深入底层逻辑的探索
【C语言】精妙运用内存函数:深入底层逻辑的探索