前言:
在c和c++中都会大量使用动态内存管理,使用c和c++实现数据结构的时候,也会使用动态内存管理
我们在写代码的时候,一般会这样向内存申请空间
比如:
int main()
{
int i = 10; //这里向内存申请4个字节的空间
int arr[10] = { 0 }; // 这里内存申请10个字节的连续空间
char c = 'c'; // 这里向内存申请1个字节的空间
return 0;
}
但是我们发现,上面的这种写法有两个特点:
1. 空间开辟的大小是固定的
2. 数组在申明的时候,必须指定数组的长度,数组空间一旦确定了大小就不能调整
但是对于空间的需求,不仅仅是上述的情况,有时候我们需要的空间大小在程序运行的时候才能知道,那数组在编译时开辟空间的方式就不能满足了。
这时c语言就提供了动态内存开辟函数,让程序员可以自己申请空间释放空间,这样就增加了灵活性。
malloc()函数
malloc()函数的声明
动态内存开辟函数
头文件 <stdlib.h>
声明:void* malloc(size_t size);
· ptr 指向开辟好的空间的起始地址
· size 要开辟多少个字节
1. 如果malloc函数开辟成功,返回一个指向开辟好了的空间的地址
2. 如果malloc函数开辟失败,返回一个空指针
所以使用malloc函数的返回值一定要做检查
3. 如果size是0,根据编译器,malloc函数可能返回一个不指向任何地址,也可能什么也不做
作用:这个函数向内存申请一块连续可用的空间(相当于数组),并返回这个指向这个空间的地址
怎么使用呢?
举例
int main()
{
//这里malloc是void*类型的指针,需要强转成自己需要的类型的指针
int* ptr = (int*)malloc(20);//这里向内存申请20个字节的空间
//这里判断malloc是否开辟成功
if (ptr == NULL)
{
//开辟失败
perror("malloc");//perror 打印malloc函数的错误信息
return 1;//提前返回
}
//开辟成功,使用空间
int i = 0;
for (i = 0; i < 5; i++)
{
*(ptr+i) = i + 1;
}
for (i = 0; i < 5; i++)
{
printf("%d ",*(ptr + i));
}
return 0;
}
malloc()函数申请的空间在内存中的存放
我们知道内存中有栈区,堆区,和静态区,代码块等等;
局部变量和形式参数是放在内存中的栈区的,malloc申请的空间是放在内存中的堆区的。
free()函数
在前面,我们向内存申请了空间,那我们不想要这个空间了,我们应该怎么把这个块空间还给操作系统了,这时候我们就要用到free()函数了,这个函数是专门用来释放动态内存空间。
free()函数的声明
动态内存释放函数
头文件<stdlib.h>
声明:void free(void* ptr);
ptr:指向的是要释放的动态内存开辟的空间的起始地址
1.如果参数ptr指向的空间不是动态内存开辟的,那么free函数什么也不做
2.如果参数ptr是NULL指针,free函数什么也不做
3.ptr指向的必须是动态内存开辟空间的起始地址,如果在别的地址,free没办法帮你释放了
作用:释放动态内存开辟的空间。
怎么使用呢?
举例:
int main()
{
//这里malloc是void*类型的指针,需要强转成自己需要的类型的指针
int* ptr = (int*)malloc(20);//这里向内存申请20个字节的空间
//这里判断malloc是否开辟成功
if (ptr == NULL)
{
//开辟失败
perror("malloc");//perror 打印malloc函数的错误信息
return 1;//提前返回
}
//开辟成功,使用空间
int i = 0;
for (i = 0; i < 5; i++)
{
*(ptr+i) = i + 1;
}
for (i = 0; i < 5; i++)
{
printf("%d ",*(ptr + i));
}
//不想使用了,还给操作系统
free(ptr);
//这里有个小知识,free完后的ptr还是指向这块空间,但是这个空间没有使用权限了,这时ptr就是野指针了
//我们需要把ptr指向NULL;
ptr = NULL;
return 0;
}
calloc()函数
calloc函数其实和malloc函数一样,也是动态内存开辟函数
calloc()函数声明
动态内存开辟函数
头文件:<stdlib.h>
声明:void* calloc(size_t num,size_t,size);
num: 要分配的个数
作用:为num个大小为size的元素开辟一块空间,并且会把空间的每个字节都初始化为0。
与malloc函数的区别只在于calloc会在返回地址之前把申请的空间的每个字节初始化为0。
怎么使用呢?
举例
int main()
{
//向内存申请5个整型的空间
int* ptr = (int*)calloc(5,sizeof(int));
//判断
if (ptr == NULL)
{
perror("calloc");
return 1;
}
//开辟成功
return 0;
}
calloc函数与malloc函数的区别
calloc与malloc函数的区别只在于calloc会在返回地址之前把申请的空间的每个字节初始化为0,
而malloc开辟的空间是未初始化的随机数。
malloc函数没有初始化,所以在速度上面会比calloc函数快一点。
malloc函数
malloc函数空间内容未初始化
当然我们也可以打印出来看一下
calloc函数
calloc函数空间内容初始化
也可以打印出来看一下
结论:如果我们要对申请的空间的内容要求初始化,我们就要calloc函数,反之用malloc函数
realloc()函数
如果我们对动态内存申请的空间太小了,或者太大了,怎么进行调整呢,这时候,就用到了我们的realloc函数了,realloc函数是对动态内存申请的空间进行大小的调整的
realloc()函数的声明
realloc函数是用来对动态内存申请的空间进行大小调整的
头文件<stdlib.h>
声明:void* realloc(void* ptr,size_t size)
ptr: 指向要调整大小的动态内存申请的空间的起始地址
size: 要调整的大小
返回值:返回调整后空间的起始地址
这个函数在调整原来的空间大小的基础上,还会把原空间里面的数据移动到新空间
怎么使用呢?
举例
int main()
{
//向内存申请20个字节大小的空间
int* ptr = (int*)malloc(20);
//判断
if (ptr == NULL)
{
perror("malloc");
return 1;
}
//申请成功,使用
//使用中...
//发现空间大小不够,需要进行调整
int* p = (int*)realloc(ptr, 40);//这里向空间增加到40个字节
//这里我们为什么不直接用ptr来直接接收调整后空间的地址,因为如果空间开辟失败,返回一个空指针给ptr接收,那么ptr里面原来的数据就没了
// 所以我们选择用同类型的指针接收调整后空间的地址
//判断
if (p == NULL)
{
perror(realloc);
return 0;
}//申请成功,可以使用40个字节的空间
ptr = p;//用ptr再接收调整后空间的地址
//这里p的作用已经结束了,我们把他释放掉
free(p);
p = NULL;
//使用完...释放
free(ptr);
ptr = NULL;
return 0;
}
realloc函数在调整空间时会的情况
realloc函数在调整空间时会出现三种情况
情况1:原有空间之后有足够大的空间
情况2:原有空间之后没有足够大的空间
情况3:调整空间失败
返回空指针。
小知识:
其实realloc函数可以完成和malloc一样的功能
int main()
{
realloc(NULL, 20);// == malloc(20)
return 0;
}
常见的动态内存的错误
1.对NULL指针的解应用
int main()
{
int* p = (int*)malloc(INT_MAX*10);
*p = 20;//如果p的值是NULL,就会有问题
return 0;
}
这里没有对malloc进行判断,如果malloc开辟失败,返回一个空指针,然后我们空指针进行了解引用。
2.对动态内存空间越界访问
int main()
{
int i = 0;
int* p = (int*)malloc(10 * sizeof(int));
if (p == NULL)
{
perror("malloc");
return 1;
}
for (i = 0; i <= 10; i++)
{
*(p + i) = i;//当i是10的时候越界访问
}
free(p);
}
这里就for循环里面的结束条件写错了,当i = 10 的时候就越界访问了
3.对非动态开辟内存使用free释放
这里我们通过调试看到编译器报警告了
4.使用free释放一块动态开辟内存的一部分
int main()
{
int* p = (int*)malloc(100);
p++;
free(p);//p不再指向动态内存的起始位置
return 0;
}
这里p++了,就不再指向动态内存的起始位置
5.对同一块动态开辟内存多次free
int main()
{
int* p = (int*)malloc(100);
free(p);
free(p);//重复释放
return 0;
}
多次使用free
这里好的方法就是,在free之后直接将它置为空指针
6.动态内存开辟后忘记释放(导致内存泄漏)
void test()
{
int* p = (int*)malloc(100);
if (p != NULL)
{
*p = 20;
}
}
//这里都是局部变量,出这个函数之后就销毁了
int main()
{
test();
//那么这里就找不到动态开辟的空间了
//现在想释放都释放不了
}
动态内存的题目
题目一
void GetMemory(char* p)
{
p = (char*)malloc(100);
}
int main()
{
char* str = NULL;
GetMemory(str);
strcpy(str, "hello world");
printf(str);
return 0;
}
运行这个代码会出现什么现象?
解析
1.内存泄漏,申请了空间没有释放
2.因为这个指针p是在函数内部创建的,出函数了之后这个p指针变量销毁了
3.那么这里的str还是空指针,strcpy对空指针拷贝数据,就会出错
题目二
char* GetMemory(void)
{
char p[] = "hello world";
return p;
}
int main()
{
char* str = NULL;
str = GetMemory();
printf(str);
}
这个代码运行会出现什么现象?
解析
这里虽然我们返回了p数组首元素的地址,但是p数组是在函数里面创建的,出这个函数之后就被销毁了,这个函数返回的就是一个野指针,我们对野指针进行访问就会出问题。
返回栈空间地址问题。
题目三
void GetMemory(char** p, int num)
{
*p = (char*)malloc(num);
}
int main()
{
char* str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello");
printf(str);
}
运行这个代码会出现什么现象?
这个代码的唯一的问题就是内存泄漏,申请空间了没有释放
题目四:
void Test(void)
{
char *str = (char *) malloc(100);
strcpy(str, "hello");
free(str);
if(str != NULL)
{
strcpy(str, "world");
printf(str);
}
这个代码,首先提前释放了,把str指向的空间还给了操作系统,但是free不会把str置为空,进入了if语句,然后strpy对已经还给操作系统的空间进行拷贝,就导致了非法访问。