动态内存分配
Suzhou 33 1

动态内存分配 内存分配方式:

  1. 创建变量(开辟一个变量空间)

函数形参(局部变量)放在栈区。 全局变量放在静态区。 2. 创建数组(开辟一块连续空间) 局部数组放在栈区。 全局数组放在静态区。

::: warning 动态开辟的内存空间一定要释放且要正确释放。 :::

当需要在数组填充数据,为了避免数组中空间浪费,需要按需分配大小。 C99标准支持可变长数组,在VS中不支持,但Linux等的gcc编译器可以支持。 动态内存分配 动态内存分配可以解决。


动态内存函数
malloc-开辟内存块

动态内存分配 (size_t:无符号整型。size:要开辟的一个内存块的字节数(类型决定)。) 函数malloc向内存中申请一块连续可用的空间,并返回指向这块空间的指针。 - 返回类型void * ,返回开辟的空间的起始地址。 - 如果开辟成功,则返回一个指向开辟好空间的指针。 - 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。 - 返回值的类型是void * ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。如果参数size为0, malloc的行为是标准是未定义的,取决于编译器。

//向内存中申请10个整型空间
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int main()
{
    int* p = (void*)malloc(10 * sizeof(int));
    //创建10个整型变量,希望以整型的形式进行维护,需要强制类型转换
    //(Linux会自动转换,vs需要手动转换)
    if (p == NULL)
    {
        printf("%s\n", strerror(errno));
        //errno是全局错误码,可以打印错误原因
    }
    else
    {
        //正常使用空间
        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;
}

malloc函数在内存空间开辟成功时候返回起始位置的指针,当没有足够的内存空间可用,返回NULL。 当动态申请的空间不再使用,系统会回收空间。函数free可以做动态内存的回收释放。

free-动态内存回收释放

动态内存分配 动态内存分配 memblock:要释放的内存空间。 free只需要要释放的内存空间的地址。 ::: tip 当没有写free函数释放空间时,函数执行到return 0,函数生命周期结束时,也会把本函数内malloc函数开辟的空间还给系统。但如果该函数代码量较多,可能会长时间占用空间,造成浪费。函数中应尽量主动回收空间。 ::: free并不改变p,即free只是该函数使用完了不再占用该空间,但p仍可以指向该空间,存在风险,应将p置为空指针。 只有动态开辟的内存空间才需要free释放。

::: danger malloc函数必须对返回值进行检测,以确保空间成功开辟。 内存开辟后使用完毕需要使用free对开辟空间进行释放,free不会改变指针,需要将指针手动置为NULL。 :::


calloc-开辟内存并初始化为0

动态内存分配 calloc的作用是为num个大小为size的元素开辟一块空间,并且把空间的每一个字节都初始化为0。 (num:需要开辟的内存块数量。size:每一个内存块的大小(类型决定)。) ::: tip calloc和malloc的区别是,calloc会在返回地址之前把申请的空间的每个字节都初始化为全0。 malloc的效率略高于calloc,但不会初始化内存空间。 :::

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

realloc-调整动态开辟内存的大小

动态内存分配 (memblock:要调整的内存块地址。size:调整后新的大小。) realloc的作用是将原来开辟的内存空间p大小调整为size。

#include <stdlib.h>
int main()
{
    //开辟一个20字节大小的内存空间
    int* p = (int*)malloc(20);
    if (p == NULL)
    {
        printf("%s\n", strerror(errno));
    }
    else
    {
        int i = 0;
        for (i = 0; i < 5; i++)
        {
            *(p + i) = i;
        }
    }
    int i = 0;
    for (i = 0; i < 5; i++)
    {
        printf("%d ", *(p + i));
    }
    int* ptr = (int*)realloc(p, 666);
    if (ptr != NULL)
    {
        p = ptr;
    }
    for (i = 5; i < 10; i++)
    {
        *(p + i) = i;
    }
    for (i = 5; i < 10; i++)
    {
        printf("%d ", *(p + i));
    }
    free(p);
    p = NULL;
    return 0;
}
0 1 2 3 4 5 6 7 8 9

上述代码调用时可以看到指针p的值会发生改变(如果没有改变,就把要追加的空间改大一些)。 ::: warning realloc函数在内存中追加空间有两种情况: 1. 原来开辟空间后的剩余空间足够追加的空间,可以在原空间的基础上向后追加,访问原来开辟空间的地址可以访问到追加后的整个内存空间,返回原来开辟内存空间的地址。 2. 原来开辟空间后的剩余空间不够追加的空间,realloc会在内存中重新开辟一块内存空间,大小等于追加后的内存空间,并将原来开辟的内存空间复制到新的内存空间。原来开辟的内存空间free并置为NULL,返回新开辟的内存空间。 动态内存分配 3. realloc返回值不能直接赋值给p。原因是realloc追加成功后会返回追加后的内存空间地址,空间过大等原因导致追加失败后,会返回NULL,如果将p赋值空指针,就会出现不但没有追加成功,反而丢失了原有内存空间的地址的情况。 :::

如果追加后返回的是新的内存空间的地址,realloc函数将把原来开辟的内存空间释放掉,如果返回的是原来开辟的内存空间地址,需要手动释放。

::: tip 如果realloc函数的第一个参数是NULL,执行的效果等同于malloc。 :::


常见动态内存错误
  • NULL指针解引用操作。 当使用malloc开辟内存空间后,没有对malloc的返回值进行有效性检查,如果malloc返回NULL,指针p会在后面的循环中多次解引用,是非法操作。

    int* p = (int*)malloc(20);
    if (p == NULL)
    {
      printf("%s\n", strerror(errno));
    }
    else
    {    int i = 0;
      for (i = 0; i < 5; i++)
      {
          *(p + i) = i;
      }
    }
  • 对动态开辟空间的越界访问。 开辟内存空间后,赋值时可能出现越界访问。

    int* p = (int*)malloc(5*sizeof(int));
    if (p == NULL)
    {
      printf("%s\n", strerror(errno));
    }
    else
    {
      int i = 0;
      for (i = 0; i < 10; i++)  //循环条件设置过大
      {
          *(p + i) = i;
      }
    }
  • 对非动态开辟的内存空间使用free。 free只能释放栈区动态开辟的内存空间,如果强行释放堆区的变量,会导致程序崩溃。

    int main()
    {
      int a = 10;
      int* p = &a;
      free(p);
      p = NULL;
      return 0;
    }
  • 使用free释放一块动态开辟内存的一部分(较为常见) 如下程序中,第14行代码中使用p++,*p的值被改变**,当循环走完以后,p指向了开辟内存空间的最后一个int后,所指向的空间是malloc所开辟空间的后面,此时使用free并将p置为空指针,会导致程序崩溃。p指向开辟的内存空间的中间执行释放依然会导致程序报错。

  • free释放空间时,必须从开辟内存空间的起始位置开始。*

    #include <stdlib.h>
    int main()
    {
       int* p = malloc(20);
       if (p == NULL)
       {
           return 0;
       }
       else
       {
           int i = 0;
           for (i = 0; i < 5; i++)
           {
               *p++ = i;  //错误
               //*(p+i) = i;  //正确
           }
       }
       free(p);
       p = NULL;
       return 0;
    }
  • 对同一块动态内存多次释放。 同一块内存空间多次释放会导致程序崩溃。

  • 为避免发生,需要遵循谁开辟谁释放、谁申请谁释放的原则。或者在free释放动态内存后,将该处内存空间置为NULL。* free不对NULL执行操作。

  • 动态开辟内存后忘记释放(内存泄漏)。 ::: danger 为了测试内存泄漏到底有多严重,我执行了下面的程序,看到内存占用100%后并没有停止,过了一会儿应该是虚拟内存也满了,电脑就黑屏了......只能断电重启了,这个笔记有一半都是重写的,一定要随手CTRL+S

    int main()
    {
      while (1)
      {
          malloc(1);
      }
      return 0;
    }

    ::: 动态内存分配

  • 上述代码中,test()函数内部开辟的动态内存空间没有释放,主函数内反复调用test()。如果test()调用结束,想要主动释放该动态内存空间都无法做到,原因是指针变量p是test()函数内的临时变量,离开test()函数,p变量被程序释放,但该空间仍存在。*

评论区

索引目录