先来理解几个概念:
- 指针的类型:指的是指针指向的变量(值)的类型,如定义一个指针变量
int *p = NULL
,指针p指向一个int型的变量,即指针的类型是int型。 - 指针指向的值的类型:与指针的类型相同,指针指向的值(变量)的类型就是指针的类型。
- 指针的值的类型:指针本身是一个地址值,这个地址值对应的内存储存着指针所指向的变量,而不管指针的类型(上面两个概念)是什么,指针变量本身的值(地址)的类型永远都是unsigned int型。
- 指针和指针变量:指针指的是地址,而指针变量指的是存放指针(地址)的变量。
int a = 0, *p;
p = &a;
在p = &a;
中,左边(p)是指针变量,右边(&a)是指针(地址)。
指针定义时分配的内存和用malloc分配的内存的区别:
- 定义指针时分配的内存是用来存放指针指向的变量的地址(指针变量本身的值)的,这个地址前面说过,是一个unsigned int型,只有几个字节(几个字节由系统决定)。
- 用malloc是申请出一段内存,用来存储数据,不是用来存储指针本身的,malloc返回开辟出的内存的首地址,存放在被赋值的指针中,使指针指向这块被开辟出来的内存。
什么时候指针需要用malloc分配内存,什么时候不需要
首先需要了解一下几点:
- C语言中,内存模型分为栈和堆,这两种模型内存的方式是不同的。
- 在栈中存放的变量是由系统自动管理的,在函数结束后系统会自动释放,不需要人为的任何操作。
- 在堆中存放的是用户自己管理的内存,手动分配的,malloc建立,系统不会在函数体执行结束后自动释放,需要用户手动通过free函数释放。
因此,当需要对分配的空间进行自己的管理和释放时,需要实用malloc,或者分配的空间再函数结束后还需要存在。
这样就有了“进击的小白——知识点:函数返回指针的应该指向什么变量”这篇博文中说的函数返回的指针应该指向什么变量,其中有一条就是“专门申请分配的空间(如用malloc)”,因为malloc申请的空间在函数执行完毕后不会自动释放。由于free后指针变量保存的地址没有改变,因此必须把指针变量的值变为NULL,否则会成为“悬空指针”。
free的作用
free执行的操作是:斩断指针变量和这块内存的对应关系。指针变量本身的值没有改变或消失,即指针变量本身保存的地址并没有改变,那块被释放的内存里面保存的值也没有改变,这是被释放的内存里保存的值被称为脏数据,可能维持原来的值,可能被清空,也可能被修改为其他的值。
关于内存泄漏
内存泄漏一般指的时堆内存,指程序从堆内存中分配出一块内存,但使用完后没有释放,导致这块内粗能不能再次使用。
首先了解一下C语言中内存分配情况,C语言中,根据数据在内存中存在的时间(生存周期)不同,讲内存空间分为三个区:
- 程序区:用于存储程序代码,即程序的二进制代码。
- 静态存储区:用于存储全局变量和静态变量,这些变量的空间在程序编译时就已经分配好了。
- 动态存储区:用于程序执行时分配的内存,又分为:堆区(heap)和栈区(stack)。堆区:用于动态内存分配,程序运行时由内存分配函数在对上分配内存,在C语言中,只能使用指针才能动态分配内存。栈区:在函数执行时,函数内存的局部变量和函数参数的存储单元的内存区域,函数运行结束时,这些内存区域会自动释放。
在使用完开辟的内存后,及时使用free释放这段内存,可以有效的避免内存泄漏。
使用malloc需要注意的几点:
- malloc返回的首地址时void *型的,因此在有指针接收这个首地址时,需要进行强制类型转换,也就是说这块内存将来需要用来存储什么类型的数据。
char *p = (char *)malloc(sizeof(char) * 100)
,这条语句的意思时在堆上分配了sizeof(char) * 100个字节的内存,返回这块内存的首地址,并把地址强制转换成char *型,赋值给char *型的指针变量p,同时告诉我们这块内存将用来存储char型的数据。这块内存只能通过指针变量p来访问,这块内存没有名字,对它的访问是匿名访问。- 当申请的内存块大于堆中剩余的内存块时,会返回NULL,可以用
if (p != NULL)
来验证内存分配是否成功。 - free后,指针变量需要重新赋值成NULL,否则会成为“悬空指针”。
- 用malloc给指针分配一块内存,然后用memset对这块内存进行初始化,就可以像操作数组一样直接操作指针:
int *p = NULL;
p = malloc(sizeof(int) * 10);
memset(p, 0, sizeof(int) * 10);
p[0] = 1;
p[1] = 2;
......