您的位置:首页 > 其它

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

2017-10-11 23:46 393 查看
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掉,避免出现野指针。

运行结果见下图:



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()函数中的第二个函数指定的就是新的内存块大小,所以即便是要缩小数组的容量,也可以用real
4000
loc()函数来达到目的,并且不会改变内存中的内容。

8.free(p)

free()一般与malloc()配对使用,千万别忘记释放内存块,这会造成内存泄露这样严重的问题。

free()函数只需要将指向该内存块的指针作为参数传入,就可以释放这块内存,注意!!!释放的是这一块内存,而不是指向这一块内存块的指针p,所以在free(p)之后,有必要将p赋值为NULL,避免之后的程序用到它,程序会崩溃的,因为这是一个野指针。

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

首先,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 = 1;
return;
}


首先,定义了结构体指针free,指向了( ptr - sizeof(struct mem_control_block) ),再将结构体中的标记变量 is_available 置为1,释放过程结束。

ptr是传入的指向内存块的指针,减去sizeof(struct mem_control_block),就是ptr向前移动了一个结构体的长度,再改变标记变量的值,使得这一块被实际申请的动态内存可用(释放成功)。

也就是说,这个标记变量就是一个开关,值为0时代表这块内存被申请占用了,被free了之后值为1就代表没有被占用了,可以用做其他的事情了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  函数 malloc
相关文章推荐