目录
前言:
本博客涉及:
1. 为什么存在动态内存分配
2. 动态内存函数的介绍
- malloc
- free
- calloc
- realloc
3. 常见的动态内存错误
1. 为什么存在动态内存分配
经过之前的学习我们知道,在C语言中,我们所使用的程序语言的初始化是一经固定,就无法改变的:
int val = 20;//在栈空间上开辟四个字节
char arr[10] = {0};//在栈空间上开辟10个字节的连续空间
这样对于一个所需要的空间,只要开辟的够大就可以使用,但是上述的开辟空间的方式有两个特点:
- 空间开辟大小是固定的。
- 数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配。
但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道, 那数组的编译时开辟空间的方式就不能满足了。 这时候就只能试试动态存开辟了。这也是,之所以会有动态内存管理的说法了。
2. 动态内存函数的介绍
(这里我们需要注意一个问题,就是动态内存管理(malloc,free,calloc,realloc)都是对内存的堆区区域进行操作的。)
2.1 malloc和free
所以,对于malloc函数我们需要注意:
- 如果开辟成功,则返回一个指向开辟好空间的指针,需要利用指针进行接收。
- 如果开辟失败,则返回一个NULL指针,进行接收后就会是一个NULL的指针,直接访问会导致程序发生错误,因此malloc的返回值一定要做检查。
- 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。
此外:
如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。不能说这个做法是错的,只能说是不标准的,而且没有知道他会发生什么……。
2.1.1 malloc空间创建成功:
#include <stdlib.h>
int main()
{
int* p = (int*)malloc(40);
if (NULL == p)
{
perror("malloc"); //打印NULL的错误原因
}
for (int i = 0; i < 10; i++)
{
*p = i;
p++;
}
return 0;
}
2.1.2 malloc空间创建失败:
例如:当你申请的空间过于的庞大的时候,就会因为空间不足,导致的无法正常开辟动态内存空间。
2.1.3 free的使用与注意点
既然,现在我们懂得了如何进行申请,那申请了不用了,也应该要还了,所以我们来讲讲,如何还动态内存。(利用free函数)
#include <stdlib.h>
int main()
{
int* ptr = (int*)malloc(40);
int* p = ptr;
if (NULL == p)
{
perror("malloc");
}
for (int i = 0; i < 10; i++)
{
*p = i;
p++;
}
free(ptr);
p = NULL;
//if (p != NULL)
//{
// *p = 1;
//}
return 0;
}
使用free所需要注意的知识点
1.如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
2.如果参数 ptr 是NULL指针,则函数什么事都不做。
3. free所释放的动态空间需要在动态内存申请的初始点,不然会导致程序崩溃。所以,我们需要先行将开辟的初始点地址进行记录,防止使用时将其改变了位置。
4. free在将内存归还于编译器后,并不会将我们之前所传输的地址进行更改,所以,我们还拿着起初的动态内存地址,但是,要知道当我们拿着不是我们所拥有的地址,不慎将其进行使用,这是十分危险的行为,为导致程序的崩溃,所以我们再归还后最好将地址改为NULL,这样在后期如若再想使用。只需进行判断其是否为NULL即可。
free的总要性:
我们要知道,free函数进行内存释放可不是用来释放的玩的,要知道,作为动态内存不是与我们平时随手创建的变量一样,他是程序结束才会自行归还的,可想而知,对于一个长时间自身循环的程序,来来回回的申请程序又不会进行自行回收,就会导致内存泄漏的问题。
2.2 calloc
其实calloc与mallloc差不多,相对于malloc来说,就是其多了一个功能,初始化数据为0;所以注意点也与malloc不尽相同。但是,同时因为calloc可以进行数据的初始化,而对于初始化,是需要进行地址的访问的,所以需要进行操作的步长的规定。
2.3 realloc
有时候我们会发现,空间很有可能会申请的空间太大,或者太小,所以就需要一个可以改变的方式那 realloc 函数就可以做到对动态开辟内存大小的调整。(realloc函数的出现让动态内存管
理更加灵活)
#include <stdlib.h>
#include <stdio.h>
int main()
{
int* ptr = (int*)malloc(40);
int* p = ptr;
if (NULL == p)
{
perror("malloc");
return 1;
}
for (int i = 0; i < 10; i++)
{
*p = i;
p++;
}
//当我们需要再扩大10个元素时
p = realloc(ptr, 80);
if (NULL == p)
{
perror("realloc");
return 1;
}
ptr = p;
//使用
free(ptr);
p = NULL;
return 0;
}
realloc在调整内存空间的是存在两种情况:
情况1 : 要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化。
情况2 :
原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另找一个合适大小的连续
空间来使用。这样函数返回的是一个新的内存地址。
注意: realloc的申请空间也不是一定成功的,当失败的时候,会返回一个NULL的指针,所以,对于此点我们尤为要注意,不要直接就用原动态内存的指针直接接收,这样会丢失原地址,导致动态内存无法查找,导致无法free掉,从而内存泄漏。
3. 常见的动态内存错误
3.1 对NULL指针的解引用操作
void test()
{
int* p = (int*)malloc(INT_MAX / 4);
*p = 20;//如果p的值是NULL,就会有问题
free(p);
}
也就是我们之前所强调的一点,malloc,calloc,realloc,如果实施失败就会返回NULL,如果我们忘记进行判断后再使用,就会出现这个问题。
3.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.3 对非动态开辟内存使用free释放
void test()
{
int a = 10;
int* p = &a;
free(p); //err
}
正如我们前面所说,这是free未进行定义的操作,本质上来说:因为,指针p指向的空间不是我们所申请的,是a这个变量在栈上的空间。我们没有动态去申请空间, 那么就没有理由释放空间。
3.4 使用free释放一块动态开辟内存的一部分
void test()
{
int* p = (int*)malloc(100);
p++;
free(p);//p不再指向动态内存的起始位置
}
需要注意:free需要对应动态内存的起始位置,如若free,p不再指向动态内存的起始位置,那么一free使用只会程序崩溃。
3.5 对同一块动态内存多次释放
void test()
{
int* p = (int*)malloc(100);
free(p);
free(p);//重复释放
}
重复的free会导致程序的崩溃。
3.6 动态开辟内存忘记释放(内存泄漏)
#include <stdlib.h>
void test()
{
int* p = (int*)malloc(100);
if (NULL != p)
{
*p = 20;
}
}
int main()
{
test();
while (1);
}
这也就是我们之前说提到的问题,如若不处理,就会导致内存泄漏,导致内存耗尽。