动态内存管理

简介: 动态内存管理

目录

前言

为什么存在动态内存分配

动态内存函数介绍

malloc

free

calloc

realloc

常见的动态内存错误

对NULL指针的解引用操作

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

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

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

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

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

柔性数组

柔性数组特点

柔性数组的优点

总结


前言

在我们C语言中,我们知道,通常我们开辟一块空间用于存放变量的时候,这块空间是不会改变大小的,但是有的时候,需要我们程序运行起来才会知道我们需要多大的内存空间,那这种情况下,我们只能创建一个很大的数组,用来存放,这个时候就出现了不必要的浪费,那这个时候,就出现了我们的动态内存。

为什么存在动态内存分配

我们一般开辟内存:

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

image.gif

但是上述的开辟空间的方式有两个特点:

1. 空间开辟大小是固定的

2. 数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配。

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


 

动态内存函数介绍

malloc

void* malloc (size_t size);

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

如果开辟成功,则返回一个指向开辟好空间的指针。

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

返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己

来决定。

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


如果我们想用malloc申请一块20个字节的空间给我们char类型的数组str用,那怎么用呢?

int main()
{
    char* str=NULL;
    str=(char*)malloc(20);
    if(str==NULL)
    {
        perror(str);
    }
    return 0;
}

image.gif

那这样就开辟了一块20个字节的空间给我们的str使用。

但是这样却会有一个致命的问题,这时候我们就要学习以下这个函数。

free

C语言提供了另外一个函数free

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

void free (void* ptr);

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

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

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

当我们用动态内存函数开辟空间之后,是需要进行内存释放的,不然就会造成内存泄漏,或者其他问题,这个时候,内存释放用的就是我们的free,它可以对我们开辟的空间进行释放,当我们进行释放之后,我们最好让先前接收这片空间的指针置空,以免造成野指针问题,那有了free之后,我们对于上面的例子代码进行升级。

free和malloc成对使用

int main()
{
    char* str=0;
    str=(char*)malloc(20);
    if(str==NULL)
    {
        perror(str);
    }
    free(str);
    str=NULL;
    return 0;
}

image.gif

calloc

void* calloc (size_t num, size_t size);

此函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0。

与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。

对于我们的malloc来说,可以申请任意字节的空间,比如17、21,那这些字节也可以强转给我们的int类型,只不过我们在往申请的这片空间中放东西的时候,因为我们的int类型是4个字节,所以放入到最后字节不够4个字节的空间时,就会产生截断,放不完全,而我们的calloc传的两个参数,第一个是要申请多少个元素,第二个是每个元素的大小,比如我们要为一个10个元素的int类型的数组申请空间,就可以像下面这样来申请:

int main()
{
    int* num=0;
    num=(int*)calloc(10,sizeof(int));
    if(num==NULL)
    {
        perror(num);
    }
    free(num);
    num=NULL;
    return 0;
}

image.gif

realloc

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

ptr 是要调整的内存地址

size 调整之后新大小

返回值为调整之后的内存起始位置。

这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间

realloc函数的出现让动态内存管理更加灵活。

有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时

候内存,我们一定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小

的调整

int main()
{
    char* str=NULL;
    str=(char*)malloc(20);
    if(str==NULL)
    {
        perror(str);
    }
    char* p=NULL;
    p=(char*)realloc(str,10);
    if(p!=NULL)
    {
        str=p;
        p=NULL;
    }
    free(str);
    str=NULL;
    return 0;
}

image.gif

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

image.gif编辑

ps.

情况1:原有空间之后有足够大的空间。

当是情况1 的时候,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化。

情况2: 原有空间之后没有足够大的空间

当是情况2 的时候,原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另找一个合适大小

的连续空间来使用。这样函数返回的是一个新的内存地址

常见的动态内存错误

对NULL指针的解引用操作

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

image.gif

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

 

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);
}

image.gif

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

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

image.gif

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

 

void test()
{
int *p = (int *)malloc(100);
p++;
free(p);//p不再指向动态内存的起始位置
}

image.gif

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

 

void test()
{
int *p = (int *)malloc(100);
free(p);
free(p);//重复释放
}

image.gif

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

 

void test()
{
int *p = (int *)malloc(100);
if(NULL != p)
{
*p = 20;
}
}
int main()
{
test();
while(1);
}

image.gif

柔性数组

柔性数组,于C99标准中定义,它处于结构体当中,当我们结构体的最后一个成员是一个未知大小的数组的时候,就是我们的柔性数组,当然,这个结构体一定要有其他的成员,废话少说那我们直接上代码。

struct st_type
{
     int i;
     int a[0];//柔性数组
};

image.gif

这种写法可能在有的编译器上跑不过,那我们就换一种写法。

struct st_type
{
     int i;
     int a[];//柔性数组
};

image.gif

柔性数组特点

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

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

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

例如:

typedef struct st_type
{
int i;
int a[0];//柔性数组成员
}type_a;
printf("%d\n", sizeof(type_a));//输出的是4

image.gif

柔性数组的优点

1>方便内存释放

当里面的成员不是柔性数组而是指针的时候,在使用的过程中,我们对其内部进行了二次的内存分配,而且把整个结构体的指针返回去了,别人使用这个结构体指针,他并不知道里面进行了二次的内存分配,他直接用free释放了整个结构体,不知道要对内部进行free,但是要是柔性数组,就没有这个问题,free一次就行了。

2>提高我们的访问速度

因为我们申请下来的空间是一片连续的空间,连续的内存有益于提高访问速度,也有益于减少内存碎片。(其实,我个人觉得也没多高了,反正你跑不了要用做偏移量的加法来寻址)

总结

动态内存大大的提高了对内存空间的利用,避免了一些不必要的内存浪费。

好了本章的内容就到此为止了,希望各位佬多多支持!

相关文章
|
5月前
|
编译器 C语言
动态内存管理(1)
动态内存管理(1)
40 4
|
5月前
|
程序员
21.动态内存管理
21.动态内存管理
|
5月前
|
存储 Linux C语言
5.C++动态内存管理(超全)
5.C++动态内存管理(超全)
|
6月前
|
程序员 编译器 C语言
带你彻头彻尾了解『动态内存管理』
带你彻头彻尾了解『动态内存管理』
|
6月前
|
编译器 程序员 C语言
动态内存管理(超详细!)
动态内存管理(超详细!)
56 2
|
6月前
|
程序员 C语言 C++
详解动态内存管理!
详解动态内存管理!
|
11月前
|
程序员 编译器
动态内存管理-1
动态内存管理
53 0
|
11月前
|
程序员 C语言 C++
动态内存管理-2
动态内存管理
42 0
|
12月前
|
C语言 C++
C++中的动态内存管理
C++中的动态内存管理
|
编译器 文件存储 数据库
Day_17> 动态内存管理
Day_17> 动态内存管理