【C语言】动态内存管理。alloca、calloc、malloc、free、realloc是什么?动态内存开辟的常见使用错误

【C语言】动态内存管理。alloca、calloc、malloc、free、realloc是什么?动态内存开辟的常见使用错误,第1张

文章目录
  • 前言
  • 一、malloc是什么?
  • 二、free是什么?
  • 三、calloc是什么?
  • 四、realloc是什么?
  • 五、动态内存开辟的常见错误
    • 1. 对NULL指针的解引用 *** 作
    • 2. 对动态开辟内存空间的越界访问
    • 3. 对非动态开辟内存使用free释放
    • 4. 使用free释放一块动态开辟内存的一部分
    • 5. 对同一块动态内存多次释放
    • 6 动态开辟内存忘记释放(内存泄漏)
  • 六、无敌经典笔试题(IMPORTANT)
    • 1. 笔试题1
    • 2. 笔试题2
    • 3. 笔试题3
    • 4. 笔试题4
  • 结语


前言

本篇文章旨在简单介绍动态内存管理的四个主要的函数,其次我们会通过一些代码演示一些使用时的常见错误,帮助读者加深动态内存管理的这四个函数。


一、malloc是什么?

众所周知,在C语言的学习中,我们经常要用到动态内存管理。即我们会根据自身程序的需要,向内存申请空间,结束释放它。而malloc正是这样的一个申请内存空间的函数。

调用形式: void* malloc (size_t size);
其中size代表向内存申请空间的大小,而void*表示malloc函数的返回类型。由于malloc不知道开辟空间的具体类型,因此我们需要自定义类型,将所开辟的空间运用到我们要使用的数据类型。
功能: 向内存申请size字节的一块内存空间
开辟成功:返回一个指向开辟好空间的指针
开辟失败: 返回一个NULL指针
分配内存空间的机制: 注意,malloc开辟的内存是在堆上进行的。malloc()从堆里面获得空间,也就是说函数返回的指针是指向堆里面的一块内存。 *** 作系统中有一个记录空闲内存地址的链表。当 *** 作系统收到程序的申请时,就会遍历该链表,然后就寻找第一个空间大于所申请空间的堆结点,然后就将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。【摘自:C语言的malloc和free函数]

使用案例:

    int* p = (int*)malloc(40); 
    //这行代码的意思是:开辟一块40字节的内存空间,
    //并且强制转化为整型数组类型,函数的返回值为指向该数组首地址的指针,并把该指针赋值给整型指针变量p。 

注意事项
由于malloc *** 作是在堆上完成的,堆的特点是你申请了空间,你不还我也不要。因此malloc使用完后一定要记得释放这块空间,否则会出现内存泄漏的情况!!而释放内存空间的函数就是free函数~(有借有还,再借不难嘛)


二、free是什么?

上文中我们讲到,free函数就是用来释放内存空间的函数,这里我们隆重介绍free~

函数原型:void free (void* ptr);
其中ptr的意思是释放ptr所指向的一块内存空间。

注意: 1. 如果ptr是空指针,那么free不会做任何事情
2. 请注意,该函数没有改变ptr本身的值,因此它仍然指向相同的位置(现在无效)。
3. 因此free完后,要记得把指针置空,防止非法访问
4. ptr一定要指向之前申请空间的首地址

使用实例:

    free(p);
    p = NULL;

三、calloc是什么?

calloc的原型:void* calloc (size_t num, size_t size);
calloc同样也是内存分配函数。其中size_t num 表示去分配的元素的数量,size_t size表示每一个元素的大小。void*表示返回的是void类型

注意: 1. calloc最大的特点是其会将每个元素的初始值置为0
2. 如果内存分配失败,则会返回一个NULL指针

应用实例:

#include
#include
int main()
{
    int* p = (int*)calloc(10, sizeof(int));

    int i = 0;
    for (i = 0; i < 10; i++)
        printf("%d  ", *(p + i));

    return 0;
}

四、realloc是什么?

函数原型:void* realloc (void* ptr, size_t size);
事实上realloc是改变ptr所指向空间的大小。其中ptr是先前开辟的内存块的指针(也就是指向malloc或calloc申请的内存空间)。size_t size新开辟内存的空间,注意这里是新开辟内存空间,并不是增加了size大小的空间。 其返回值依旧是void*,使用者可以根据自己的需要转化为所需要的类型。

返回值: 其返回值是void*类型,并且指向新开辟空间的地址,这个地址即可能和原来的地址相同,也可能和原来地址不同,这取决于原来地址后面是否能放下新开辟空间。

注意

  1. 如果ptr是一个空指针,那么realloc的功能和malloc功能相识,同样开辟size大小的内存空间,并返回这块空间的启示地址。
  2. 如果开辟失败,realloc会返回一个空指针。
  3. 如果ptr指向的空间之后有足够的空间可以追加,则直接追加,返回的是p原来的起始地址
  4. 如果ptr指向的空间之后没有足够的空间可以追,那么realloc会找到一块新的内存空间,再把原来内存空间的数据拷贝过来,释放旧的内存空间还给 *** 作系统,最后返回新开辟的内存空间的起始地址。
五、动态内存开辟的常见错误 1. 对NULL指针的解引用 *** 作
    int* p = (int*)malloc(INT_MAX);
    *p = 20;
    free(p);
    p = NULL;
    return 0;

注意这里malloc分配内存失败,返回的是一个空指针,对p直接解引用使用非常危险,会导致程序崩溃。

2. 对动态开辟内存空间的越界访问
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);
}

这里会形成十分严重的越界访问。

3. 对非动态开辟内存使用free释放
void test()
{
 int a = 10;
 int *p = &a;
 free(p);
}

对非动态开辟内存使用free释放会导致程序崩溃。

4. 使用free释放一块动态开辟内存的一部分
void test()
{
 int *p = (int *)malloc(100);
 p++;
 free(p);
}

这里p不再指向动态内存的起始位置,只释放了动态开辟内存空间的一部分,依然会导致程序崩溃。

5. 对同一块动态内存多次释放
void test()
{
 int *p = (int *)malloc(100);
 free(p);
 free(p);//重复释放
}

对同一块动态内存多次释放依然会导致程序崩溃,但如下所示没有问题。因此指针free完后置为空指针是一个非常优秀的习惯。

void test()
{
 int *p = (int *)malloc(100);
 free(p);
 p=NULL;
 free(p);//这里没有问题,因为p在之前置空了,而free对空指针不会做任何处理。
    
}
6 动态开辟内存忘记释放(内存泄漏)
void test()
{
 int *p = (int *)malloc(100);
 if(NULL != p)
 {
 *p = 20;
 }
}
int main()
{
 test();
 while(1);
}

注意如果函数内部有动态内存开辟的空间,一定要记得将其地址返回并且置空。如果忘记释放会造成严重的内存泄漏。

六、无敌经典笔试题(IMPORTANT) 1. 笔试题1
void GetMemory(char *p)
{
 p = (char *)malloc(100);
}
void Test(void)
{
 char *str = NULL;
 GetMemory(str);
 strcpy(str, "hello world");
 printf(str);
}

解析:这道题的关键在于p和str并非同一个指针。具体来说Test函数首先创建了一个char* 类型的指针变量str,并置空。 接着调用GetMemory函数,并把str作为形参传给GetMemory。GetMemory实参部分用char *p指针接收,这个时候p里面存放的就是NULL。函数运行,申请了100个字节的空间,并把该空间的首地址赋值给p。函数结束后,str的值依然是NULL。这里函数传参用的是值传递,因此该函数不会修改str的值,只会修改p的值。因此当执行strcpy(str, “hello world”)时,把hello world拷贝到空指针str中,编译器崩溃。

正确代码如下:


    void GetMemory(char** p)  //二级指针用来存放一个指针的地址
    {
        *p = (char*)malloc(100);  //*p找到str
    }


    void Test(void)
    {
        char* str = NULL;
        GetMemory(&str);
        strcpy(str, "hello world");
        printf(str);
        free(str);
        str = NULL;
    }

    int main()
    {
        Test();
         return 0;
    }

解析:这里通过地址传递,用二级指针p来接收指针str的地址。然后通过解引用p找到str,并且把动态开辟空间的地址赋值给str。以上,指针str存的就是动态开辟内存空间的地址,strcpy就能正常运行了。

2. 笔试题2
char *GetMemory(void)
{
 char p[] = "hello world";
 return p;
}
void Test(void)
{
 char *str = NULL;
 str = GetMemory();
 printf(str);
}

解析:这道题的关键是创建的字符数组p在出函数后,这块空间已经返回给 *** 作系统了。因此尽管函数返回了hello world的首地址,但是hello world本身的内容已经没有了。因此无法正常打印。这种问题类型是非常典型的返回栈空间地址的问题。

3. 笔试题3
void GetMemory(char **p, int num)
{
 *p = (char *)malloc(num);
}
void Test(void)
{
 char *str = NULL;
 GetMemory(&str, 100);
 strcpy(str, "hello");
 printf(str);
}

这段代码有什么问题呢?细心的小伙伴一定会发现。这段代码动态申请了一段内存空间,但是没有free释放,极易造成内存泄漏的问题。
因此我们修改代码,一定要free掉。修改如下:

void GetMemory(char **p, int num)
{
 *p = (char *)malloc(num);
}
void Test(void)
{
 char *str = NULL;
 GetMemory(&str, 100);
 strcpy(str, "hello");
 printf(str);
 free(str);
 str=NULL;
}
4. 笔试题4
void Test(void)
{
    char* str = (char*)malloc(100);
    strcpy(str, "hello");
    free(str);
    if (str != NULL)
    {
        strcpy(str, "world");  //str是野指针,这里非法访问了
        printf(str);
    }
}

这道题非法访问。free(str)后,str后面的这块空间已经不属于我们了。因此我们不能再去使用。
修改如下:

void Test(void)
{
    char* str = (char*)malloc(100);
    if (str == NULL)
        return;

    strcpy(str, "hello");
    free(str);
    str = NULL;

    if (str != NULL)
    {
        strcpy(str, "world");
        printf(str);
    }
}

因此我们free后,一定要把指针置空。这样可以有效防止野指针和非法访问的问题。

结语

本篇文章我们介绍了alloca、calloc、malloc、free、realloc这四个函数的基本用法,以及在使用这些函数时,我们可能遇到的常见错误。
如果本文有错误,欢迎各位大佬指正(玻璃心,轻喷)
最后祝大家学习进步,功不唐捐!

欢迎分享,转载请注明来源:内存溢出

原文地址:https://www.54852.com/langs/3002528.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-09-27
下一篇2022-09-27

发表评论

登录后才能评论

评论列表(0条)

    保存