目录
前言
这篇文章讲解了C语言动态内存管理方面的内容,适合初学了解和知识回顾,包括一些主要的动态内存函数的介绍,以及一些常见的动态内存错误。
一、为什么需要动态内存分配?
char arr[20]={0};
在栈空间上开辟20个连续的空间
上面开辟的空间是我们最常用的方式,也就是静态内存分配,具有两个特点:
1. 空间开辟大小是固定的。
2. 数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配。
二、常见动态内存函数的介绍
1.1 malloc函数
void* malloc (size_t size);
这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。
1.2 free函数
void free (void* ptr); //专门是用来做动态内存的释放和回收的
free函数用来释放动态开辟的内存。
如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
malloc和free都声明在 stdlib.h 头文件中。
1.3 calloc函数
calloc 函数也用来动态内存分配
void* calloc ( size_t num , size_t size );
函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0。
#include <stdio.h>
#include <stdlib.h>
int main()
{
int* p = (int*)calloc(10, sizeof(int));
if (NULL != p)
{
//使用空间
}
free(p);
p = NULL;
return 0;
}
调试监视内存验证函数功能:
1.4 realloc函数
void* realloc ( void* ptr , size_t size );//ptr 是要调整的内存地址
//size 调整之后新大小
//返回值为调整之后的内存起始位置。
//这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间。
realloc在调整内存空间的是存在两种情况:
1:原有空间之后有足够大的空间
三、常见的动态内存错误
3.1 对NULL指针的解引用操作
void test()
{
int *p = (int *)malloc(INT_MAX/4);
*p = 20;//如果p的值是NULL,就会有问题
free(p);
}
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);
}
对非动态开辟内存是不能用free释放的,如果释放则会触发断点
3.4 使用free释放一块动态开辟内存的一部分
void test()
{
int *p = (int *)malloc(100);
p++;
free(p);//p不再指向动态内存的起始位置
}
经过测试依旧会触发断点
3.5 对同一块动态内存多次释放
void test()
{
int *p = (int *)malloc(100);
free(p);
free(p);//重复释放
}
这些常见的动态内存错误都会引发断点 ,所以要正确释放空间。
3.6 动态开辟内存忘记释放(内存泄漏)
void test()
{
int *p = (int *)malloc(100);
if(NULL != p)
{
*p = 20;
}
}
忘记释放不再使用的动态开辟的空间会造成内存泄漏
这种错误编译器不会报错,因为编译器是不会检查内存泄露的,但是我们应该切记动态开辟的空间一定要释放,并且正确释放 。
四 、一些经典的笔试题
4.1
void GetMemory(char *p)
{
p = (char *)malloc(100);
}
void Test(void)
{
char *str = NULL;
GetMemory(str);
strcpy(str, "hello world");
printf(str);
}
请问运行Test 函数会有什么样的结果?
无法打印hello world
因为p是形参,只是str的一份临时拷贝,开辟的空间存放到地址p,str并没有改变
str依旧为NULL,而且在strcpy函数中对str空指针进行了解引用,程序会崩溃
而且开辟的空间没有释放,存在内存泄漏问题,而且p已经销毁,想要free掉也找不到空间的地址。
如果改为传址则可以改变str,使用二级指针:
4.2
char *GetMemory(void)
{
char p[] = "hello world";
return p;
}
void Test(void)
{
char *str = NULL;
str = GetMemory();
printf(str);
}
请问运行Test 函数会有什么样的结果?
p数组是在函数内部被创建的,p指向的空间被销毁了,数组回收,所以只能打印出随机值
虽然str记住了p的地址,但是函数执行完后数组空间已经被销毁了,str就是野指针,调用野指针打印只能打印随机值,每次打印结果都不一样。相当于非法访问。
4.3
void GetMemory(char **p, int num)
{
*p = (char *)malloc(num);
}
void Test(void)
{
char *str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello");
printf(str);
}
请问运行Test 函数会有什么样的结果?
内存泄漏,没有释放掉malloc的空间
添加free(str); str=NULL;
4.4
void Test(void)
{
char *str = (char *) malloc(100);
strcpy(str, "hello");
free(str);
if(str != NULL)
{
strcpy(str, "world");
printf(str);
}
}
str的空间已经被释放,而且释放后没有将str置空str=NULL;
会造成非法访问。
五、 C/C++程序的内存开辟
1. 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。2. 堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分配方式类似于链表。3. 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。4. 代码段:存放函数体(类成员函数和全局函数)的二进制代码。