详解C语言中动态内存管理及柔性数组的使用

 更新时间:2022年07月18日 08:40:03   作者:蒋灵瑜的流水账  
这篇文章主要为大家详细介绍一下C语言中动态内存管理以及柔性数组的使用方法,文中的示例代码讲解详细,对我们学习C语言有一定的帮助,需要的可以参考一下

一、malloc

这个函数向堆区申请一块连续的空间,并返回这块内存的地址。

int main()
{
    int* p = (int*)malloc(40);
    if (p == NULL)
    {
        printf("%s\n", strerror(errno));
        return 1;
    }
    free(p);
    p = NULL;
    return 0;
}

如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。

如果参数 size为0,malloc的行为是标准是未定义的,取决于编译器。

二、free

free函数用来释放动态开辟的内存。free的指针必须是指向动态内存空间的起始地址。

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

如果参数 ptr 是NULL指针,则函数什么事都不做。

三、calloc

num:要动态开辟的元素个数

size:每个元素的大小

calloc=malloc+memset

calloc和malloc的区别就在于calloc会将动态内存开辟的空间全部初始化为0

四、realloc

ptr:需要扩容的原始地址

size:扩容后的总大小

1、realloc在扩容时的情况

1、扩容时后方空间足够,将在后方继续扩容,返回原始地址

2、扩容时后方空间已被使用,将在新的一块区域进行扩容,并将原始数据拷贝至新的区域,free原始地址指向的内存,并返回内存块的地址。

3、空间不足,无法扩容,返回空指针

2、realloc也能实现malloc功能

int main()
{
    int* p = (int*)realloc(NULL, 40);
    return 0;
}

realloc在当malloc使用时,第一个参数传空指针即可。

五、使用动态内存的常见错误

1、free空指针

void text()
{
    int* p = (int*)realloc(NULL, 40);
    if (p == NULL)//判断p是否为空指针
    {
        return;
    }
    *p = 20;
}

如果开辟空间后,没有判断指针p是否是空指针,如果动态内存开辟失败,那么*p将会解引用空指针。

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

void text()
{
    int* p = (int*)realloc(NULL, 40);
    int* m = p;
    if (p == NULL)
    {
        return;
    }
    for (int i = 0; i <= 10; i++)
    {
        p[i] = i;//越界
        p++;
    }
}

3、对非动态开辟内容free

void text()
{
    int arr[] = {1,2,3};
    int* p = arr;
    free(p);//不能对非动态开辟的空间进行free
}

4、只free动态开辟空间的一部分

void text()
{
    int* p = (int*)malloc(40);
    p++;
    free(p);//p不再指向动态开辟空间的起始位置
}

实际写代码的时候还是得多注意这种情况,指针p在使用的过程中已经不再是初始位置,free(p)会崩溃。

5、对同一块内存多次free

void text()
{
    int* p = (int*)realloc(NULL, 40);
    free(p);
    //p = NULL;
 
    free(p);
}

free(p)后及时将p置为空指针,哪怕p被多次free也没问题。

6、动态内存空间忘记释放(内存泄漏)

void text()
{
    int* p = (int*)realloc(NULL, 40);
    int a = 0;
    scanf("%d", &a);
    if (a == 5)
        return;
    free(p);
    p = NULL;
}

当函数提前终止,没有机会走到free,可能会造成内存泄漏

另一种可能是调用了动态开辟内存的函数,使用者未在外部进行free

六、柔性数组

1、柔性数组的概念

C99中,结构体中最后一个成员变量可以是未知大小的数组

struct S
{
    int a;
    int arr[0];//这里的arr[0]代表未知大小的数组
};
//或者如下写法:
struct S
{
    int a;
    int arr[];
};

2、柔性数组的特点

结构体中的柔性数组成员前面必须至少一个其他成员。

sizeof 返回的这种结构大小不包括柔性数组的内存。

包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。

3、柔性数组的使用场景

场景:将结构体中所有的成员变量全部在堆区开辟

使用柔性数组:

struct S
{
    int a;
    int arr[0];
};
int main()
{
    struct S s;
    struct S* ps = &s;
    ps = (struct S*)realloc(NULL,sizeof(s) + 40);//首次开辟空间,传NULL
    if (ps == NULL)
    {
        exit(-1);
    }
    free(ps);
    ps = NULL;
    return 0;
}

注意此处的realloc第一个参数传入的是NULL而不是&s,传入&s会崩溃,是因为这是第一次动态内存开辟,此处传入NULL相当于使用malloc

完成开辟后s在内存中的存储如下图:

使用常规结构体:

struct S
{
    int a;
    int* parr;
};
int main()
{
    struct S* p = (struct S*)malloc(sizeof(struct S));//对结构体动态开辟空间
    if (p == NULL)
    {
        exit(-1);
    }
    int* tmp= (int*)malloc(sizeof(int) * 2);//在堆区动态开辟一块空间
    if (tmp == NULL)
    {
        exit(-1);
    }
    p->parr = tmp;
    free(p->parr);//释放时,需要先释放p->parr指向的空间
    p->parr = NULL;
    free(p);//再将结构体指针p指向的空间释放
    p = NULL;
    return 0;
}

完成开辟后s在内存中的存储如下图:

4、柔性数组的优点

1、在上述条件下,使用柔性数组方便动态内存释放。如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把整个结构体返回给用户。用户调用free可以释放结构体,但是用户并不知道这个结构体内的成员也需要free,可能会造成内存泄漏。所以,如果我们把结构体的内存以及其成员要的内存一次性分配好了,并返回给用户一个结构体指针,用户做一次free就可以把所有的内存给释放掉。

2、连续的内存有益于提高访问速度,也有益于减少内存碎片。

以上就是详解C语言中动态内存管理及柔性数组的使用的详细内容,更多关于C语言 动态内存管理 柔性数组的资料请关注脚本之家其它相关文章!

相关文章

  • 使用c++实现OpenCV绘制旋转矩形图形

    使用c++实现OpenCV绘制旋转矩形图形

    这篇文章主要给大家介绍了使用c++实现OpenCV绘制图形旋转矩形的方法案例,通过图文及代码形式进行了详细的描述,有需要的朋友可以参考下,希望可以有所帮助
    2021-08-08
  • C++虚函数表的原理与使用解析

    C++虚函数表的原理与使用解析

    对C++ 了解的人都应该知道虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。简称为V-Table。本文就将详细讲讲虚函数表的原理与使用,需要的可以参考一下
    2022-04-04
  • C++17中的std::from_chars函数使用及说明

    C++17中的std::from_chars函数使用及说明

    这篇文章主要介绍了C++17中的std::from_chars函数使用及说明,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2025-10-10
  • C++设计模式之中介者模式

    C++设计模式之中介者模式

    这篇文章主要介绍了C++设计模式之中介者模式,本文讲解了什么是中介者模式、中介者模式的使用场合、中介者模式的优缺点等内容,需要的朋友可以参考下
    2014-10-10
  • C语言中volatile关键字的作用及说明

    C语言中volatile关键字的作用及说明

    文中主要介绍了C语言中volatile关键字的含义和使用场景,volatile是一个类型修饰符,主要用来修饰被不同线程访问和修改的变量,它的作用是防止编译器对代码进行优化,确保每次直接读取原始内存地址的值
    2024-10-10
  • C++实现LeetCode(27.移除元素)

    C++实现LeetCode(27.移除元素)

    这篇文章主要介绍了C++实现LeetCode(27.移除元素),本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-07-07
  • visual studio 建立dll类型工程、控制台程序

    visual studio 建立dll类型工程、控制台程序

    这篇文章主要介绍了visual studio 建立dll、控制台类型工程的相关知识,感兴趣的朋友跟随脚本之家小编一起学习吧
    2018-05-05
  • 构造函数定义为private或者protected的好处

    构造函数定义为private或者protected的好处

    从语法上来讲,一个函数被声明为protected或者private,那么这个函数就不能从“外部”直接被调用了。对于protected的函数,子类的“内部”的其他函数可以调用之。而对于private的函数,只能被本类“内部”的其他函数说调用
    2013-10-10
  • C/C++ 运用Npcap发送UDP数据包的完美过程

    C/C++ 运用Npcap发送UDP数据包的完美过程

    UDP 是一种无连接、轻量级的传输层协议,与 TCP 相比,它不提供可靠性、流控制和错误恢复机制,但却更加简单且具有较低的开销,这篇文章主要介绍了C/C++ 运用Npcap发送UDP数据包,需要的朋友可以参考下
    2023-11-11
  • C++实现KFC点餐系统

    C++实现KFC点餐系统

    这篇文章主要为大家详细介绍了C++实现KFC点餐系统,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-07-07

最新评论