欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

申请动态内存——malloc()函数及其扩展函数

程序员文章站 2022-05-12 08:33:27
...

1.malloc()概述——申请动态内存

malloc(num)向系统申请num字节的动态内存,内存于“堆”里存放,若申请成功,则函数返回(无类型)数组的首地址,失败则返回NULL,并且申请之后的内存中并没有初始化。该函数需要引用头文件——stdlib.h。
由于“堆”有一个特性——由程序自行管理内存,所以在申请了动态内存之后,需要利用free()自行释放,这是为了避免出现野指针,并且把指向这块内存的指针指向NULL,防止之后的程序再用到这个指针。如果不自行释放的话,就会造成内存泄露——可用内存越来越少,设备速度越来越慢。
检验是否出现内存泄露的工具VLD,下载、配置好之后只需要在程序中加上头文件——vld.h,在调试状态下的输出栏就可以知道是否发生内存泄露。

2.malloc() 代码:

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int *p = (int *)malloc( sizeof(int)*10 );//申请了40个字节的动态内存

    for(int i=0; i<10; i++)
    {
        p[i] = i;
    }

    for(int i=0; i<10; i++)
    {
        printf("%d\n",p[i]);
    }

    free(p);
    p = NULL://防止后面的程序再用到
    return 0;
}

声明为:void* malloc(unsigned size);其中,size为申请的总字节大小。
malloc()函数返回的是一个无类型的指针(void *),所以可以根据程序的需要来对其进行强转。
在本例中,申请了40字节的动态内存,将返回的地址强转为int *型,再将地址赋给了整型指针变量 p,那么p就是指向了一个含有10个int型数据的数组,再对其进行赋值、打印。
使用完动态内存之后,最后free掉,避免出现野指针。

运行结果见下图:

申请动态内存——malloc()函数及其扩展函数

3.calloc()概述——申请动态内存且进行初始化

该函数的声明为:void* calloc(size_t numElements, size_t sizeOfElement); 其中第一个参数代表的是元素个数,第二个参数代表的是这些数据的类型。
calloc()函数在malloc()函数的基础上,将申请的动态内存里的数据进行了初始化,把它们都初始化为0。
calloc()实现的初始化置零操作,可以被malloc()替代,即在使用malloc()申请完动态内存之后再通过其他的代码块实现初始化置零,与calloc()是等价的。(见以下代码)

4.calloc() 代码

int main()
{
    int *p = (int *)malloc( sizeof(int)*10 );
    for(int i=0; i<10; i++)
    {
        p[i] = 0;
    }

    //与上述操作作用相同
    int *q = (int *)calloc(10, sizeof(int));

    return 0;
}

5.realloc()概述——对原动态内存块进行扩容

该函数的声明为:void* realloc(void* ptr, unsigned newsize);其中第一个参数指的是原内存块的首地址,第二个参数指的是新的动态数组的字节大小。
如果,在使用malloc()申请的动态内存之后,发现不够用了,这时扩容函数就排上用场了。

int main()
{
    int i;
    int *p = (int *)malloc( 10*sizeof(int) );
    for(i=0; i<10; i++)
    {
        p[i] = i;
    }

    //在使用的过程中,发现p的长度不够用了,需要20个格子
    int *q = (int *)malloc( 20*sizeof(int) );
    for(i=0; i<10; i++)
    {
        q[i] = p[i];//搬家
    }

    //free(p);//拆旧家//考虑到之后要free q,因为同一个程序里,不能free两次,这里就不直接free p了

    p = q;//改地址
    q = NULL;//拆旧家//这步很关键,两个指针不能同时指向同一个地址 —> 浅拷贝

    free(p);

    //与上述操作相同
    int *p = (int *)malloc( 10*sizeof(int) );
    for(i=0; i<10; i++)
    {
        p[i] = i;
    }//不够用
    p = (int*)realloc( p, 20*sizeof(int) );
    free(p);
    p = NULL;

    return 0;
}

7.如何缩小数组的容量?

其实,realloc()函数中的第二个函数指定的就是新的内存块大小,所以即便是要缩小数组的容量,也可以用realloc()函数来达到目的,并且不会改变内存中的内容。

8.free(p)

free()一般与malloc()配对使用,千万别忘记释放内存块,这会造成内存泄露这样严重的问题。
free()函数只需要将指向该内存块的指针作为参数传入,就可以释放这块内存,注意!!!释放的是这一块内存,而不是指向这一块内存块的指针p,所以在free(p)之后,有必要将p赋值为NULL,避免之后的程序用到它,程序会崩溃的,因为这是一个野指针。

9.free(p)释放内存的时候,是如何读取长度信息的呢?

首先,malloc()实际上申请的内存块会稍大于它实际申请的字节数,见下图。

申请动态内存——malloc()函数及其扩展函数

第一部分:可用空间,用来存放用户所需数据的地方。第二部分:管理信息,这就是多出来的部分,用来存放分配块的长度,指向下一个分配块的指针等信息。
free()就是通过读取第二部分中的管理信息来释放内存块的。(具体释放过程有机会补充啊~)
如果,将指向内存块的指针p移动了,那么内存块中的管理信息也会发生变化,释放的时候就会出现问题,这就是free()释放失败的其中一个原因。第二个原因——重复释放,但是重复释放空指针是个例外,是可以重复的,只不过没有实际意义。第三个原因——越界,既然free是通过读取内存块的管理信息来达到正确释放的目的,那么越界释放就是错的了,不能正确读取释放信息。

10.free()的实现过程

malloc()申请的内存块中,记录管理信息部分的实际上是一个结构体,见下。

struct mem_control_block 
 {

    int is_available;    //一个标记

    int size;            //实际空间的大小

 };

我们可以看到在结构体mem_control_block 中,有两个管理信息——标记变量和内存块的实际大小,其中,标记变量的作用是什么呢?这恰恰就是free()所需要的一个重要信息。
再看,free()的源代码:

void free(void *ptr) 
{
     struct mem_control_block *free;

     free = ptr - sizeof(struct mem_control_block);

     free->is_available = 1return;
}

首先,定义了结构体指针free,指向了( ptr - sizeof(struct mem_control_block) ),再将结构体中的标记变量 is_available 置为1,释放过程结束。
ptr是传入的指向内存块的指针,减去sizeof(struct mem_control_block),就是ptr向前移动了一个结构体的长度,再改变标记变量的值,使得这一块被实际申请的动态内存可用(释放成功)。
也就是说,这个标记变量就是一个开关,值为0时代表这块内存被申请占用了,被free了之后值为1就代表没有被占用了,可以用做其他的事情了。

相关标签: 函数 malloc