文章目录
目录
前言
为什么存在动态内存分配?
动态内存分配的函数有哪些以及如何使用?
动态内存开辟的错误通常有哪些?
本文章将以最简洁的语言的实际用例帮助阅读这篇博客的小伙伴快速掌握动态内存函数的使用方法。
下面进入正文
一、为什么存在动态内存分配?
当使用数组存放数据,随着数据的增多但空间却没有增长,而且每时每刻维护代码让人不厌其烦.....
基于大空间以及灵活开辟的需求,动态内存函数正是基于此想法的实现,通过特定函数和设计方法可以根据数据的增多随时随地的开辟空间
二、动态内存函数的介绍
1.malloc
void* malloc (size_t size);
malloc函数的参数为size_t size,返回值void*
也就是说,使用此函数时传入数值,然后malloc函数返回一个起始空间的地址指针
int* p=(int *)malloc(40);
由于返回类型为 void* 的指针,在赋值给其他创建的指针时根据其指针类型对malloc强制转换,常量值40代表开辟40个字节的空间
malloc需要的只是数值,所以一些计算方法可以方便地为我们开辟想要的空间大小
int *p = (int *)malloc(10 * sizeof(int));
此方法最大的好处是我们可以直接sizeof去计算自己的结构体或者利用一些数据类型去直接开辟,避免了数字节而出错
2.free
void free (viod* ptr);
free函数可以将申请的动态空间释放掉
因为动态内存分配是在堆区上进行,如果是一个7*24小时的服务器去运行某一段没有free函数的动态内存代码,最后服务器内存耗干造成内存泄漏的问题
#include<stdio.h>
int main()
{
int *p=(int *)malloc(10*sizeof(int));
//
//
//
//使用空间
//
//
//
//释放空间
free(p);
p=NULL;
retrun 0;
}
因为当free释放掉p指向的动态空间后并不会将p置为NULL,手动置空是为了防止野指针问题
当使用任何动态内存的函数都应该该函数与free成对出现,不使用指针时置NULL保证代码健壮性
3.calloc
void* calloc(size_t num,size_t size);
num代表可需要元素的个数,size则是该元素的大小
calloc与malloc看起来相似但在细节上面略有差异,malloc将空间开辟好后不会进行初始化,而calloc函数在开辟好指定空间后将空间内部初始化为0
根据需求灵活选择malloc与calloc函数
4.realloc
void* realloc(void * ptr,size_t size);
reallo函数的作用是当动态空间不足时对其增容
不过realloc增容会有两种情况
1.假如后面空间足够。将对其空间尾部进行续接
2.假设后面的空间不过指定增容的大小(一般来说是增加空间的数值较大),则找寻一块足够大的空间开辟上指定大小,再将原始空间的内容拷贝到新空间然后销毁旧空间
举个实际例子:
#include <stdio.h>
int main()
{
int *ptr = (int*)malloc(100);
if(ptr != NULL)
{
//业务处理
}
else
{
exit(EXIT_FAILURE);
}
//扩展容量
//代码1
ptr = (int*)realloc(ptr, 1000);//这样可以吗?(如果申请失败会如何?)
//代码2
int*p = NULL;
p = realloc(ptr, 1000);
if(p != NULL)
{
ptr = p;
}
//业务处理
free(ptr);
return 0;
}
三、常见的动态内存错误
1.对NULL空指针解引用操作
int main()
{
int* p = (int*)malloc(INT_MAX);
*p = 20;
free(p);
p=NULL;
return 0;
}
首先,INT_MAX是一个非常大的整数,这里断然malloc是无法开辟成功的,如果开辟失败malloc会返回NULL
这里是我内存够大导致用INT_MAX是成功的
故此手改成一个非常大的值来展示malloc开辟失败的确会返回NULL
int main()
{
int* p = (int*)malloc(INT_MAX);
*p = 20;
free(p);
p=NULL;
return 0;
}
所以问题很明显,假设失败后我们对于一个空指针解引用操作,属于非法访问,所以正确的写法是在开辟完成后对p判断是否为空
2.对于动态空间的越界访问
int main()
{
int* p = (int*)malloc(20);
if (p == NULL)
return 1;
int i = 0;
for (i = 0; i < 20; i++)
{
*(p + i) = i;
}
return 0;
}
此代码有两处错误:
1.对于动态内存开辟的空间进行越界访问
2.没有对指针p进行free释放和置NULL
3.对非动态开辟空间内存使用free释放
int main()
{
int a = 0;
int* P = &a;
free(P);
return 0;
}
由于动态内存分配是在堆区上进行而局部变量的开辟是在栈区上进行,所以对栈区上的空间free会出错
4.使用free释放一块动态内存的一部分
int main()
{
int* p = (int*)malloc(100);
p++;
free(p);
return 0;
}
free释放的空间必须从空间的起始位置开始,此时的p++改变了p的指向
5.对同一块空间多次释放
int main()
{
int* p = (int*)malloc(100);
free(p);
free(p);
return 0;
}
重复释放造成的野指针问题!
第一次free释放完成后并不置空导致指针指向空间是非系统分配的内存,再次释放等同于对其非法访问。
6.动态内存空间的忘记释放
int main()
{
int* p = (int*)malloc(10 * sizeof(int));
if (p == NULL)
return 1;
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = i;
}
return 0;
}
这个问题是经典的也是致命的!
上面有提到过如果是一个7*24小时运行的服务器如果运行此代码会导致内存泄漏(也就是内存被耗干)
四、几个经典的笔试题
1.
void GetMemory(char*p)
{
p = (char*)malloc(100);
}
void test()
{
char* str = NULL;
GetMemory(str);
strcpy(str, "hello,world");
printf(str);
}
int main()
{
test();
return 0;
}
给大家一分钟时间想一想
这道题的问题是对NULL指针解引用
看似GetMemory函数开辟了一百个字节的空间供其拷贝
但我们知道传值调用中形参只是实参的一份临时拷贝,对于形参的修改是不会影响实参的
所以,空间开辟了指针没返回,自然导致对NULL解引用
2.
char* GetMemory(void)
{
char p[] = "hello world";
return p;
}
int main()
{
char* str = NULL;
str = GetMemory;
return 0;
}
虽然看着代码很正确但很遗憾,但它是问题代码
为什么这么说?
首先我们得讨论一下局部变量的生命周期和作用域的概念,char* p这个数组是在GetMemory的函数中开辟当此函数调用完成后此段代码生命周期结束然后将空间销毁并还给操作系统
也就是说,str实际接收的是一个野指针
这段代码的经典错误就是返回了栈空间的地址
3.
void GetMemory(char*p,int num)
{
*p= (char*)malloc(num);
}
int main()
{
char* str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello");
return 0;
}
这段代码的开辟和返回是没有问题的
但是你要这么想就大错特错,这段代码的问题还是跟我举例的7*24小时服务器的问题一模一样
也就是内存泄漏的问题
4.
int main()
{
char* str = (char*)malloc(100);
strcpy(str, "hello");
free(str);
if(NULL!=str)
{
strcpy(str, "world");
printf(str);
}
return 0;
}
这段代码我想大家一眼便能看出这又是个野指针的问题
当free完成后并没有把str置为空,所以下面if判断是可以进入并执行其中的strcpy对野指针拷贝”world“字符串
完结感言:
对于动态内存分配的函数和常见错误就给大家介绍完毕
虽然很多但都是一些干货,尤其列举出的一些错误都是经常出现的,但当我们编写的代码量愈来愈多时总会出现一些奇奇怪怪,甚至于无法察觉的细微错误
所有我们一定要持有一颗谨慎的心去编写我们的代码!
希望在大家在编程的学习之路上一帆风顺,愿顶峰相见!