一篇文章玩转C语言动态内存,轻松管理动态内存空间

Posted Jiawen_captial

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一篇文章玩转C语言动态内存,轻松管理动态内存空间相关的知识,希望对你有一定的参考价值。

1、动态内存分配存在的原因

我们普遍定义变量的时候,经常采用以下的写法:

char str;//在栈上开辟1个字节的内存空间
int num;//在栈上开辟4个字节的内存空间
int arr[10];//在栈上开辟40字节连续的空间

但是上述的开辟空间的方式有两个特点:
1、空间开辟大小是固定的。
2、数组在声明的时候,必须指定数组的长度,他所需要的内存在编译时分配。

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

2、动态内存函数的介绍

  • C语言提供了一些动态内存开辟的函数:使得存储空间的使用更加灵活,提高内存的利用率。
  • malloc和free都声明在 stdlib.h 头文件中。所以在使用的时候要引相应的头文件,下面让我们来看一下这些函数的用法。

2.1malloc函数

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

void* malloc(size_t size);

向内存堆区申请size个字节的空间,申请完毕后返回堆区的地址,因为不知道返回什么类型的地址合适,所以返回void*类型的。
2、如果开辟成功,则返回一个指向开辟好空间的指针。

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

4、返回值的类型是 void*,所以malloc函数并不知道开辟空间的类型,具体在使用的时候由使用者自己来决定。一般情况下不拿void*来接收,要强制类型转换后进行使用。

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

6、在使用malloc开辟空间的时候,一定要判断是否开辟成功

int main()
{
    int *p=(int*)malloc(40);
    if(p==NULL)
    {
        return -1;
    }
    int i=0;
    //开辟的空间地址是连续的,可以看成数组
    for(i=0;i<10;i++)
    {
        *(p+i)=i;
    }
   
}

我们申请空间,使用空间,在使用完成后我们要释放空间,此时就用到了free函数。

2.2free函数

C语言提供了另外一个函数free,专门是用来做动态内存的释放和回收的,函数原型如下:
1、free函数的介绍:

void free (void* ptr);

free函数用来释放动态开辟的内存。
2、free的使用:

int main()
{
    int *p=(int*)malloc(40);
    if(p==NULL)
    {
        return -1;
    }
    int i=0;
    //开辟的空间地址是连续的,可以看成数组
    for(i=0;i<10;i++)
    {
        *(p+i)=i;
    }
    free(p);
    p=NULL;
   
}

free把p的空间给释放掉了,但是不会改变p的地址。这种释放后依然能找到free释放的空间是非常可怕的,所以要将释放的空间置空。

3、如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。free也不知所措了。

4、如果参数 ptr 是NULL指针,则函数什么事都不做。所以可以给free传一个空指针,但是free什么事都不会干。

2.3calloc函数

C语言还提供了一个函数叫 calloc , calloc函数也用来动态内存分配。原型如下:

void* calloc (size_t num, size_t size);

1、calloc函数介绍:num表示元素的个数,size表示一个元素有多大,单位字节

#incldue<stdio.h>
#include<errno.h>
#include<string.h>
int main()
{
    int *p=(int*)calloc(10,sizeof(int));
    //开辟10个大小为4字节的连续空间,并且初始化为0
    if(p==NULL)
    {
        printf("%s\\n",strerror(errno));
        return -1;
    }
    free(p);
    p=NULL;
    return 0;
}

2、malloc函数只负责在堆区申请空间,并且返回起始地址,不初始化内存空间;calloc函数在堆区上申请空间,并且初始化为0,返回起始地址。

3、如果我们对申请的内存空间的内容要求初始化,那么可以很方便的使用calloc函数来完成任务。

2.4realloc函数

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

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

1、realloc函数的介绍:ptr是要调整的内存地址,之前可能有malloc或者calloc开辟好的空间;size 为调整之后新大小,单位字节。

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

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

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

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

1)情况1:原有空间之后有足够大的空间,要扩展内存就直接在原有内存之后直接追加空间,原来空间的数据不发生变化。把原先空间的地址当作新地址返回。

2)情况2:原有空间之后没有足够大的空间,扩展的方法是:在堆空间上另找一个合适大小的连续空间来使用。并将原空间的数据拷贝在新空间上,在释放掉原空间,这样函数返回的是一个新的内存地址。

6、 由于上述的两种情况,realloc开辟失败会返回空指针,如果贸然使用,开辟失败可能会丢失原有空间,所以realloc函数的使用就要注意进行判断,在进行自身的赋值。 举个例子:

#incldue<stdio.h>
#include<errno.h>
#include<string.h>
int main()
{
    int *p=(int*)calloc(10,sizeof(int));
    if(p==NULL)
    {
        printf("%s\\n",strerror(errno));
        return -1;
    }
    int* ptr=(int*)realloc(p,20*sizeof(int));
    if(ptr!=NULL)
    {
    p=ptr;
    }
    free(p);
    p=NULL;
    return 0;
}

3、常见的动态内存错误

3.1对NULL空指针的解引用操作

int main()
{
    int *p=(int*) malloc(20);
    *p=0;//这样不进行判断,直接解引用是有风险的。
    if(p==NULL)
    {
        return -1;
    }
}

结论:所以对动态内存开辟的空间一定要先判断在使用。如下:

int main()
{
    int *p=(int*) malloc(20);
    if(p==NULL)
    {
        return -1;
    }
    *p=0;
}

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

int main()
{
    int *p=(int*)malloc(200);
    if(p==NULL)
    {
        return -1;
    }
    int i=0;
    //200字节也就是50个左右的整型,访问80个越界访问了,程序会直接崩掉
    for(i=0;i<80;i++)
    {
        *(p+i)=i;
    }
    free(p);
    p=NULL:
    return 0;
}

结论:在对动态开辟的内存使用时,一定要注意范围。

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

int main()
{
    int a=10;
    int* p=&a;
    free(p);
    //处在栈上的空间直接free,即非堆上的空间释放程序会直接崩掉。
    p=NULL;
    return 0;
}

结论:对非堆上的内存不可以使用free释放。

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

int main()
{
    int* p=(int*)malloc(10*sizeof(int));
    if(p==NULL)
    {
        return -1;
    }
    int i=0;
    for(i=0;i<10;i++)
    {
        *p++=i;
//p在++的过程中,p已经不再执行开辟空间的起始位置,free掉的只是现在位置往后的空间。    
  
    }
    free(p);
    p=NULL;
    return 0;
}

结论:++/–在某些情况下是具有副作用的,对于地址的起始位置是非常重要的,如果要用到地址的偏移,可以先保存原先地址的起始位置。即给原地址进行一个备份。

3.5对同一块内存的多次释放

int main()
{
    int* p=(int*)malloc(40);
    if(p==NULL)
    {
        return -1;
    }
    free(p);
    free(p);
    //程序直接崩溃
    return 0;
}

另一种错误:

int main()
{
    int* p=(int*)malloc(40);
    if(p==NULL)
    {
        return -1;
    }
    free(p);
    p=NULL;
    free(p);
    //此时程序可以正常运行
    return 0;
}

结论:对释放的空间一定要进行及时的置空,并且不能重复释放同一空间。即使有两个指向同一内存的,也只能free一个,因为释放完之后,原有的内存空间已经释放掉了,在进行释放就是重复释放。

4、后续有动态内存的面试题及其运用,收藏+关注不迷路哟。

以上是关于一篇文章玩转C语言动态内存,轻松管理动态内存空间的主要内容,如果未能解决你的问题,请参考以下文章

玩转C语言动态内存,轻松管理动态内存空间

C语言学习笔记(16)动态内存管理

深度解刨C语言内存管理(详)

C语言之动态内存管理(动态内存分配+经典笔试题+柔性数组)[建议收藏]

C语言进阶四.动态内存管理

C语言动态内存