
- 前言
- 一、malloc是什么?
- 二、free是什么?
- 三、calloc是什么?
- 四、realloc是什么?
- 五、动态内存开辟的常见错误
- 1. 对NULL指针的解引用 *** 作
- 2. 对动态开辟内存空间的越界访问
- 3. 对非动态开辟内存使用free释放
- 4. 使用free释放一块动态开辟内存的一部分
- 5. 对同一块动态内存多次释放
- 6 动态开辟内存忘记释放(内存泄漏)
- 六、无敌经典笔试题(IMPORTANT)
- 1. 笔试题1
- 2. 笔试题2
- 3. 笔试题3
- 4. 笔试题4
- 结语
前言
本篇文章旨在简单介绍动态内存管理的四个主要的函数,其次我们会通过一些代码演示一些使用时的常见错误,帮助读者加深动态内存管理的这四个函数。
一、malloc是什么?
众所周知,在C语言的学习中,我们经常要用到动态内存管理。即我们会根据自身程序的需要,向内存申请空间,结束释放它。而malloc正是这样的一个申请内存空间的函数。
调用形式: void* malloc (size_t size);
其中size代表向内存申请空间的大小,而void*表示malloc函数的返回类型。由于malloc不知道开辟空间的具体类型,因此我们需要自定义类型,将所开辟的空间运用到我们要使用的数据类型。
功能: 向内存申请size字节的一块内存空间
开辟成功:返回一个指向开辟好空间的指针
开辟失败: 返回一个NULL指针
分配内存空间的机制: 注意,malloc开辟的内存是在堆上进行的。malloc()从堆里面获得空间,也就是说函数返回的指针是指向堆里面的一块内存。 *** 作系统中有一个记录空闲内存地址的链表。当 *** 作系统收到程序的申请时,就会遍历该链表,然后就寻找第一个空间大于所申请空间的堆结点,然后就将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。【摘自:C语言的malloc和free函数]
使用案例:
int* p = (int*)malloc(40);
//这行代码的意思是:开辟一块40字节的内存空间,
//并且强制转化为整型数组类型,函数的返回值为指向该数组首地址的指针,并把该指针赋值给整型指针变量p。
注意事项
由于malloc *** 作是在堆上完成的,堆的特点是你申请了空间,你不还我也不要。因此malloc使用完后一定要记得释放这块空间,否则会出现内存泄漏的情况!!而释放内存空间的函数就是free函数~(有借有还,再借不难嘛)
二、free是什么?
上文中我们讲到,free函数就是用来释放内存空间的函数,这里我们隆重介绍free~
函数原型:void free (void* ptr);
其中ptr的意思是释放ptr所指向的一块内存空间。
注意: 1. 如果ptr是空指针,那么free不会做任何事情
2. 请注意,该函数没有改变ptr本身的值,因此它仍然指向相同的位置(现在无效)。
3. 因此free完后,要记得把指针置空,防止非法访问
4. ptr一定要指向之前申请空间的首地址
使用实例:
free(p);
p = NULL;
三、calloc是什么?
calloc的原型:void* calloc (size_t num, size_t size);
calloc同样也是内存分配函数。其中size_t num 表示去分配的元素的数量,size_t size表示每一个元素的大小。void*表示返回的是void类型
注意: 1. calloc最大的特点是其会将每个元素的初始值置为0
2. 如果内存分配失败,则会返回一个NULL指针
应用实例:
#include
#include
int main()
{
int* p = (int*)calloc(10, sizeof(int));
int i = 0;
for (i = 0; i < 10; i++)
printf("%d ", *(p + i));
return 0;
}
四、realloc是什么?
函数原型:void* realloc (void* ptr, size_t size);
事实上realloc是改变ptr所指向空间的大小。其中ptr是先前开辟的内存块的指针(也就是指向malloc或calloc申请的内存空间)。size_t size新开辟内存的空间,注意这里是新开辟内存空间,并不是增加了size大小的空间。 其返回值依旧是void*,使用者可以根据自己的需要转化为所需要的类型。
返回值: 其返回值是void*类型,并且指向新开辟空间的地址,这个地址即可能和原来的地址相同,也可能和原来地址不同,这取决于原来地址后面是否能放下新开辟空间。
注意
- 如果ptr是一个空指针,那么realloc的功能和malloc功能相识,同样开辟size大小的内存空间,并返回这块空间的启示地址。
- 如果开辟失败,realloc会返回一个空指针。
- 如果ptr指向的空间之后有足够的空间可以追加,则直接追加,返回的是p原来的起始地址
- 如果ptr指向的空间之后没有足够的空间可以追,那么realloc会找到一块新的内存空间,再把原来内存空间的数据拷贝过来,释放旧的内存空间还给 *** 作系统,最后返回新开辟的内存空间的起始地址。
int* p = (int*)malloc(INT_MAX);
*p = 20;
free(p);
p = NULL;
return 0;
注意这里malloc分配内存失败,返回的是一个空指针,对p直接解引用使用非常危险,会导致程序崩溃。
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. 对非动态开辟内存使用free释放void test()
{
int a = 10;
int *p = &a;
free(p);
}
对非动态开辟内存使用free释放会导致程序崩溃。
4. 使用free释放一块动态开辟内存的一部分void test()
{
int *p = (int *)malloc(100);
p++;
free(p);
}
这里p不再指向动态内存的起始位置,只释放了动态开辟内存空间的一部分,依然会导致程序崩溃。
5. 对同一块动态内存多次释放void test()
{
int *p = (int *)malloc(100);
free(p);
free(p);//重复释放
}
对同一块动态内存多次释放依然会导致程序崩溃,但如下所示没有问题。因此指针free完后置为空指针是一个非常优秀的习惯。
void test()
{
int *p = (int *)malloc(100);
free(p);
p=NULL;
free(p);//这里没有问题,因为p在之前置空了,而free对空指针不会做任何处理。
}
6 动态开辟内存忘记释放(内存泄漏)
void test()
{
int *p = (int *)malloc(100);
if(NULL != p)
{
*p = 20;
}
}
int main()
{
test();
while(1);
}
注意如果函数内部有动态内存开辟的空间,一定要记得将其地址返回并且置空。如果忘记释放会造成严重的内存泄漏。
六、无敌经典笔试题(IMPORTANT) 1. 笔试题1void GetMemory(char *p)
{
p = (char *)malloc(100);
}
void Test(void)
{
char *str = NULL;
GetMemory(str);
strcpy(str, "hello world");
printf(str);
}
解析:这道题的关键在于p和str并非同一个指针。具体来说Test函数首先创建了一个char* 类型的指针变量str,并置空。 接着调用GetMemory函数,并把str作为形参传给GetMemory。GetMemory实参部分用char *p指针接收,这个时候p里面存放的就是NULL。函数运行,申请了100个字节的空间,并把该空间的首地址赋值给p。函数结束后,str的值依然是NULL。这里函数传参用的是值传递,因此该函数不会修改str的值,只会修改p的值。因此当执行strcpy(str, “hello world”)时,把hello world拷贝到空指针str中,编译器崩溃。
正确代码如下:
void GetMemory(char** p) //二级指针用来存放一个指针的地址
{
*p = (char*)malloc(100); //*p找到str
}
void Test(void)
{
char* str = NULL;
GetMemory(&str);
strcpy(str, "hello world");
printf(str);
free(str);
str = NULL;
}
int main()
{
Test();
return 0;
}
解析:这里通过地址传递,用二级指针p来接收指针str的地址。然后通过解引用p找到str,并且把动态开辟空间的地址赋值给str。以上,指针str存的就是动态开辟内存空间的地址,strcpy就能正常运行了。
2. 笔试题2char *GetMemory(void)
{
char p[] = "hello world";
return p;
}
void Test(void)
{
char *str = NULL;
str = GetMemory();
printf(str);
}
解析:这道题的关键是创建的字符数组p在出函数后,这块空间已经返回给 *** 作系统了。因此尽管函数返回了hello world的首地址,但是hello world本身的内容已经没有了。因此无法正常打印。这种问题类型是非常典型的返回栈空间地址的问题。
3. 笔试题3void GetMemory(char **p, int num)
{
*p = (char *)malloc(num);
}
void Test(void)
{
char *str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello");
printf(str);
}
这段代码有什么问题呢?细心的小伙伴一定会发现。这段代码动态申请了一段内存空间,但是没有free释放,极易造成内存泄漏的问题。
因此我们修改代码,一定要free掉。修改如下:
void GetMemory(char **p, int num)
{
*p = (char *)malloc(num);
}
void Test(void)
{
char *str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello");
printf(str);
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"); //str是野指针,这里非法访问了
printf(str);
}
}
这道题非法访问。free(str)后,str后面的这块空间已经不属于我们了。因此我们不能再去使用。
修改如下:
void Test(void)
{
char* str = (char*)malloc(100);
if (str == NULL)
return;
strcpy(str, "hello");
free(str);
str = NULL;
if (str != NULL)
{
strcpy(str, "world");
printf(str);
}
}
因此我们free后,一定要把指针置空。这样可以有效防止野指针和非法访问的问题。
结语本篇文章我们介绍了alloca、calloc、malloc、free、realloc这四个函数的基本用法,以及在使用这些函数时,我们可能遇到的常见错误。
如果本文有错误,欢迎各位大佬指正(玻璃心,轻喷)
最后祝大家学习进步,功不唐捐!
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)