- 前言
- 一、常用的动态内存函数
- 1.malloc和free
- 2.calloc
- 3.realloc
- 二、常见的动态内存错误
- 1.利用realloc扩容时容易犯的错
- 2.对空指针进行解引用操作
- 3.对动态内存开辟的空间越界访问
- 4.对非动态开辟的空间进行free
- 5.使用free释放一块动态开辟内存的一部分
- 6.对一块空间重复free
- 7.动态开辟的空间忘记释放
- 三.c/c++的内存分配
- 四.经典面试题
- 题目一
- 题目二
- 题目三
- 题目四
- 总结
前言
动态内存管理在c语言中是很重要的内容,它与数据结构紧密相关,所以我们需要对动态内存进行深入探究。
一、常用的动态内存函数 1.malloc和freemalloc的函数原型如下:
void* malloc (size_t size);
该函数向内存空间申请一块连续可用的空间,并返回指向这块空间的指针。
1.如果开辟成功,则返回一个指向开辟好空间的指针。
2.如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
3.返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。
4.如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。
free的函数原型如下:
void free (void* ptr);
该函数用来释放动态内存开辟的空间:
1.如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
2.如果参数 ptr 是NULL指针,则函数什么事都不做。
下面这段代码便是malloc和free的使用方法:
int* ptr = NULL;
ptr = (int*)malloc(num*sizeof(int));
if(NULL != ptr)//判断ptr指针是否为空
{
int i = 0;
for(i=0; i
*(ptr+i) = 0;
}
}
free(ptr);//释放ptr所指向的动态内存
ptr = NULL;//是否有必要?
return 0;
}
2.calloc
calloc的函数原型如下:
void* calloc (size_t num, size_t size);
这个函数功能和malloc一样,只是会把空间的每个字节初始化为0
realloc的函数原型如下:
void realloc(void* ptr , size_t size)
该函数的用法如下:
1.ptr 是要调整的内存地址
2.size 调整之后新大小
3.返回值为调整之后的内存起始位置。
4.这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间
当该函数开辟新的空间时:
如果原内存空间后面的空间足够,则会直接在后面追加一块空间;
如果原内存空间后面的空间不够,那么便会在堆上找一块空间,并返回这个新的地址
当我们了解了这几个基本函数后,我们便可以深入研究动态内存的知识。
1.利用realloc扩容时容易犯的错请看如下代码有什么问题:
int main()
{
int* p = (int*)malloc(20);
if (p != NULL)
{
;
}
p = (int*)realloc(p, 40);//这一步有问题吗?
if(p!=NULL)
printf("%pn", p);
free(p);
p = NULL;
return 0;
}
我们可以看到编译器给了如下警告:
如果我们realloc失败,那么会返回一个NULL指针,而这个空指针被赋值给p,那么原始内存块就会被泄露,我们无法再通过free来释放这块空间。
代码如下:
void test()
{
int *p = (int *)malloc(INT_MAX/4);
*p = 20;//如果p的值是NULL,就会有问题
free(p);
}
当malloc失败时,会返回一个NULL指针,而我们是不能对空指针进行解引用的。所以我们需要判断,当p不为空时才能进行后续操作。
3.对动态内存开辟的空间越界访问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);
}
4.对非动态开辟的空间进行free
void test()
{
int a = 10;
int *p = &a;
free(p);//ok?
}
5.使用free释放一块动态开辟内存的一部分
void test()
{
int *p = (int *)malloc(100);
p++;
free(p);//p不再指向动态内存的起始位置
}
6.对一块空间重复free
void test()
{
int *p = (int *)malloc(100);
free(p);
free(p);//重复释放
}
7.动态开辟的空间忘记释放
void test()
{
int *p = (int *)malloc(100);
if(NULL != p)
{
*p = 20;
}
}
int main()
{
test();
while(1);
}
我们不仅要在合适的地方释放空间,还要正确的释放空间。
三.c/c++的内存分配四.经典面试题 题目一
- 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。
- 堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分配方式类似于链表。
- 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。
- 代码段:存放函数体(类成员函数和全局函数)的二进制代码。
p是一个形参,在调用GetMemory后,p被销毁,开辟的那块内存则泄露了。同时,str传进去时为null,而调用GetMemory时str未改变,所以还是null,程序崩溃。
当然,我们可以修改一下代码解决这两个问题。
我们需要str接收到开辟内存的那块地址,所以我们需要传str的地址进去,然后我们就可以利用这块空间,在用完之后释放即可。
该代码的运行结果是:
接下来我们分析一下原因:
GetMemory函数返回一个地址,但数组p是该函数的一个局部变量,调用结束后被销毁,str就会接受一个野指针,指向的空间位置未知,可能是原内容也可能被覆盖,所以打印的内容不确定。
该代码运行的结果是:
接下来我们分析一下这段代码:
该代码能打印hello是因为接收了开辟的那块空间的地址,但有一个问题,开辟的空间没有free,所以会造成内存泄漏。
该代码运行的结果是:
我们分析这段代码:str被free,它指向的那块空间被销毁,还给了操作系统,但str还是存着那块空间的地址,所以不为空,打印了world。str是一个野指针,造成了非法访问。
我们需要注意这个问题:当free一块空间时,还需要将指向这块空间的指针置为null,才能避免非法访问。
动态内存管理是一个很重要的内容,其内存泄漏也是很容易忽视的问题,所以我们需要深入学习并养成良好,避免在写代码的过程中犯错。



