C语言——【动态内存管理】

目录

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

二、动态内存函数的介绍

2.1 malloc / free

2.2 calloc

 2.3 realloc

三、常见的动态内存的错误

3.1 对NULL指针的解引用操作

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

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

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

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

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

四、C/C++程序的内存开辟(简略介绍)

五、柔性数组

5.1 柔性数组的特点

5.2 使用示例

5.3 柔性数组的优势


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

我们可以这样开辟空间:int arr[5] = {0};

这样开辟的空间有局限性:开辟大小固定,并且开辟时必须指定数组长度。这样会有的时候浪费内存,有的时候不够存放,很不方便。于是就有了动态内存管理。

※浅浅介绍一下电脑中内存的分配:分为栈区(存放局部变量,函数的形式参数);堆区(malloc/free calloc realloc 动态内存分配);静态区(全局变量 静态变量)


二、动态内存函数的介绍

2.1 malloc / free

传统申请内存方式:

int main()
{
    int arr1[10]; //申请40个字节
    char arr2[40];  //40个字节
    return 0;
}

传统的存在局限性。用malloc函数可以开辟动态内存,那么就让我们来了解一下malloc的用法:

C语言——【动态内存管理】_第1张图片

malloc——申请内存块,传入大小,返回void*的指针,指向开辟的空间。

释放malloc申请的内存空间是用函数free:

C语言——【动态内存管理】_第2张图片 free可以把申请的空间还给操作系统。

使用示例如下:

int main()
{
    int arr1[10]; //申请40个字节
    char arr2[40];  //40个字节
    
    //申请空间
    int* ptr = (int*)malloc(40);  //等同于 int ptr[10];
    int* p = ptr; //一般不动申请的起始地址,会用别的变量表示然后动别的变量,所以这里不动ptr,动p
    if(p == NULL)    //如果申请失败,则报错
    {
        perror("malloc");
        return 1;
    }
    
    for(int i = 0; i<10; i++)  //将开辟的空间初始化为0 1 2 3 4 5 6 7 8 9
    {
        *p = i;
        p++;
    }
    
    //释放空间
    free(ptr);
    ptr = NULL; //不一定需要,但这样更安全
    return 0;
}

如果malloc之后不free(只申请空间不释放空间):1.如果程序结束,动态申请的内存由操作系统自动回收 2.如果程序不结束,动态内存是不会自动回收的,就会形成内存泄漏的问题。所以记得malloc之后要free!

2.2 calloc

也是一种动态内存分配的函数。

C语言——【动态内存管理】_第3张图片

malloc函数申请空间的初始化是随机值,calloc申请空间的初始化时0。

例:

int main()
{
    int* p = calloc(10, sizeof(int));
    if(p == NULL) //如果失败,显示失败原因
    {
        perror("calloc");
        return 1;
    }
    //申请成功,使用
    for(int i = 0; i<10; i++)
    {
        printf("%d ", *(p+i));
    }
    free(p);
    p = NULL;
    return 0;
}

 2.3 realloc

重新开辟。当我们觉得空间不够了,可以用realloc再开辟的大一些。(realloc可扩大也可以缩小)

C语言——【动态内存管理】_第4张图片

int main()
{
    int* p = (int*)malloc(40);

    if(p == NULL) //如果失败,显示失败原因
    {
        perror("malloc");
        return 1;
    }
    //申请成功,使用
    for(int i = 0; i<10; i++)
    {
        *(p+i) = i;  //初始化为0 1 2 3 4 5 6 7 8 9
    }
    //空间不够,希望放20个元素,考虑扩容
    int* ptr = (int*)realloc(p, 80);  //如果扩容失败会返回NULL,所以不能直接赋值给p
    if(ptr!=NULL)
    {
        p = ptr;
    }
    //扩容成功了,开始使用
    //此处省略使用的代码
    free(p);
    p = NULL;
    return 0;
}

三、常见的动态内存的错误

3.1 对NULL指针的解引用操作

解决办法:对malloc函数的返回值进行判空操作。

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

解决方法:写代码的时候注意边界。

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

局部变量不需要我们释放。

解决方法:写代码的时候小心点。

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

解决方法:记住只能释放起始位置!

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

解决方法:多动脑子记住你有没有释放。

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

解决方法:记得释放!


四、C/C++程序的内存开辟(简略介绍)

内存中其实不止有栈区、堆区、静态区(数据段),还有内核空间(用户代码不可读写)、内存映射段、代码段(存放可执行代码的二进制指令/只读常量)


五、柔性数组

结构体中最后一个元素允许是未知大小的数组,这就叫做柔性数组成员。

struct S
{
    int num;
    double d;
    int arr[];   //柔性数组成员
};

struct S2
{
    int num;
    double d;
    int arr[0];  //柔性数组成员
};

5.1 柔性数组的特点

  • 柔性数组前面必须至少一个其他成员
  • sizeof返回的这种结构大小不包括柔性数组的内存
  • 包含柔性数组成员的结构用malloc进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小

5.2 使用示例

struct S3
{
    int num;
    int arr[];
};

int main()
{
    struct S3* ps = (struct S3*)malloc(sizeof(struct S3)+40);
    if(ps = NULL)
    {
        perror("malloc\n");
        return 1;
    }
    ps->num = 100;
    int i = 0;
    for(i = 0; i<10; i++)
    {
        ps->arr[i] = i;
    }
    for(i = 0; i<10; i++)
    {
        printf("%d ", ps->arr[i]);
    }

    //如果要扩容
    struct S3* ptr = (struct S3*)realloc(ps, sizeof(struct S3)+80);
    if(ptr == NULL)
    {
        perror("realloc\n");
        return 1;
    }
    else
    {
        ps = ptr;
    }

    free(ps);
    ps = NULL;
    return 0;
}

5.3 柔性数组的优势

1.方便内存释放(一次释放即可)

2.有利于访问速度,有益于减少内存碎片


这就是有关动态内存管理的内容啦~欢迎交流!

你可能感兴趣的:(c++,c语言,c#)