目录
1.动态内存函数
1.1.malloc和free函数
1.2.calloc函数
1.3.realloc函数
1.4.总结
2.常见的动态内存错误
2.1.对NULL指针的解引用操作
2.2.对动态开辟的空间的越界访问
2.3.对非动态开辟内存使用free释放
2.4.使用free释放一块动态开辟内存的一部分
2.5.对同一块动态内存多次释放
2.6动态开辟内存忘记释放(内存泄漏)
2.7.总结
前言:为什么要有动态内存管理呢?
之前我们在学习创建变量、数组,等等都是申请空间来放置数值;一旦申请之后,空间的大小不会再改变。本文将介绍C语言中申请内存大小的函数 malloc、calloc、realloc、free,以及对内存函数使用时候常见的错误。若有错误,请各位指正。
1.动态内存函数
动态内存函数是针对堆区来说的,下图是内存的大致划分,静态内存就是静态区中的全局变量和静态修饰的局部变量。如下代码是static修饰的局部变量,属于静态区。
static a = 0;
1.1.malloc和free函数
1.功能:
malloc:向内存申请一块连续可用的空间,返回指向这块空间起始的指针;
free: 释放动态开辟的内存。
2.使用形式
void* malloc (size_t size);
void free (void* ptr);
malloc函数
1.void* 返回值类型为空指针,需要强转换再使用,申请成功返回内存块起始指针,申请内存失败,返回位空指针;
2.size 内存块的大小,字节位单位,size_t无符号整型;
注意:
1.malloc函数申请的空间是不会初始化的,内存块不初始化是随机值。
2.在free释放内存块之后,void* 函数的返回值保留之前的开辟内存块的时候指针变量,
如果再访问就是非法访问了,因此使用完内存空间要将返回值置为NULL,可看示例。
free函数
1.viod没有返回值;
2.ptr 指向先前使用或者分配的内存块的指针,如果ptr是空指针NULL,则函数不会执行任何操作。
注意:free函数的形式参数(ptr)是指向动态内存空间的变量。
3.示例
(1)正常使用
#include <stdio.h>
#include <stdlib.h>
int main ()
{//申请动态内存空间int* p = (int*)malloc(40);//判断是否申请成功,并强制转换为整型if (p == NULL){perror("malloc");return 1;}//赋值int i = 0;for (i = 0; i < 10; i++){*(p + i) = i;}//输出for (i = 0; i < 10; i++){printf(" %d",*(p + i) ); }return 0;//用完之后返回给堆区,以便下次的调用free(p);p = NULL;
}
(2)申请错误的案例
#include <stdio.h>
#include <stdlib.h>
int main()
{//申请动态内存空间int* p = (int*)malloc(INT_MAX);//INT_MAX宏定义,表示int中最大的值2147483647//16进制是0x7FFFFFFF//判断是否申请成功,并强制转换为整型if (p == NULL){perror("malloc");return 1;}free(p);p = NULL;
}
INT_MAX问宏定义,表示int 整型中最大的值,2147483647,开辟空间可能已经超过给函数分配的空间,输出的结果是没有足够的空间。
(3)越界访问示例
#include <stdio.h>
#include <stdlib.h>
int main()
{//申请动态内存空间int* p = (int*)malloc(40);//判断是否申请成功,并强制转换为整型if (p == NULL){perror("malloc");return 1;}free(p);p = NULL;//赋值int i = 0;for (i = 0; i < 10; i++){*(p + i) = i;}
}
这种情况下是无法写入的。
(4)free函数释放非动态空间
#include <stdio.h>
#include <stdlib.h>
int main()
{int a = 0;int* p = &a;//创建非动态空间的指针free(p);}
释放非动态空间的指针会直接报错的。
1.2.calloc函数
1.功能:Allocate and zero-initialize array分配内存并将其初始化为0.
2.使用形式:
void* calloc (size_t num, size_t size);
1.void* 同上;返回值是指向调整之后内存的起始位置;
2.num:分配元素的个数;;
3.size:每个元素的大小;
注意:
1.calloc函数和malloc函数不同,申请空间之后会将其 初始化为0;其余的作用基本一样。
3.示例
#include <stdio.h>
#include <stdlib.h>
int main()
{int* p1 = (int*)malloc(10 * sizeof(int));int* p2 = (int*)calloc(10 ,sizeof(int));//两者的表达方式相似,只是*改为了,//判断是否申请成功if (p1 == NULL){perror("malloc");return 1;}else if (p2 == NULL){perror("calloc");return 1;}//使用int i = 0;for (i = 0; i < 10; i++){*(p2 + i) = i;}//输出for (i = 0; i < 10; i++){printf(" %d",*(p2 + i) ); }free(p1);free(p2);p1 = NULL;p2 = NULL;return 0;
}
其中p1是malloc函数申请的内存,p2是calloc申请的内存,可以看出calloc函数在申请完内存之后就会初始化为0.
1.3.realloc函数
1.功能:Reallocate memory block,重新分配内存块的大小,扩展原有的内存块,或者申请新的内存块,
特点:保留之前的内存中的数据。
2.使用形式:
void* realloc (void* ptr, size_t size);
1.void* 同上,返回值是指向调整之后内存的起始位置;
2.ptr 先前使用或者分配的内存块的大小;
3.内存块的大小。
注意:
1.和malloc一样不会初始化空间;
2.申请空间失败的将返回NULL空指针;
3.如果ptr是空指针,则该函数的行为类似于 malloc,分配一个新的字节块并返回一个指向其开头的指针;
4.realloc函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间。
5.realloc函数是重新申请内存块,不是追加在malloc内存空间的内存块,是将原本的空间扩展到size的大小,整个内存块空间的大小就是size,和malloc再开辟的空间相比,函数只是可以保留剩之前已经赋值的内存空间。
4.realloc调整内存空间的两种情况
假设 malloc函数申请了5个整型的空间,但是不够是使用了,要是使用 realloc函数将malloc 的空间扩展为10个整型的空间,可以分为两种情况。假设其返回值分别为p1和p2。
(1)p1==p2的情况
malloc申请的空间可以扩容,可以在后面直接续上新的空间,malloc函数返回值和realloc函数的返回值是一样的。
(2)p1!=p2的情况
当malloc申请的空间后面没有足够的空间扩容,realloc函数会重新昭仪满足空间大小的连续的空间把就得空间数据,拷贝到新空间的对应的位置,并把旧的空间释放掉,同时返回新的内存块的起始位置。
3.示例:
#include <stdio.h>
#include <stdlib.h>
int main()
{int* p = (int*)malloc(5*sizeof(int));//申请5个整型数据的空间if (p == NULL){perror("malloc");return 1;}int i = 0;for (i = 0; i < 5; i++){*(p + i) = i;}int* str = realloc(p, 10* sizeof(int));//重新申请的内存空间if (str == NULL){perror("realloc");return 1;}else{p = str;//情况1,空间已经被释放了,p=str;//情况2, 新开辟的内存空间,之前malloc开辟的空间已经释放,p不为NULL//如果统一使用,建议使用p}for (; i < 10; i++){*(p + i) = i;}for (i = 0; i < 10; i++){printf(" %d", *(p + i));}free(str);//realloc函数延续之前分配的空间继续使用或者释放掉之前的空间,p = NULL;str = NULL;return 0;}
1.4.总结
malloc函数和realloc函数申请的函数空间不会初始化,calloc申请的函数空间会初始化,realloc申请的空间是在malloc和calloc空间后面的追加,free配合前面的三个函数使用。动态内存函数所设计到的操作系统的设置我不会啊,所以没有能力模拟函数实现。
2.常见的动态内存错误
2.1.对NULL指针的解引用操作
#include <stdio.h>
#include <stdlib.h>
void test()
{int *p = (int *)malloc(INT_MAX );*p = 20;//如果p的值是NULL,就会有free(p);
}
int main()
{test();return 0;
}
上述的代码中malloc函数没有申请成功内存空间,也不会报错出错误在哪里,因此在对返回指针进行解引用操作的之前需要判断是否为空指针。
2.2.对动态开辟的空间的越界访问
#include <stdio.h>
#include <stdlib.h>
int main ()
{//申请动态内存空间int* p = (int*)malloc(100);//判断是否申请成功,并强制转换为整型if (p == NULL)//判断是否为空指针{perror("malloc");return 1;}//赋值int i = 0;for (i = 0; i < 100; i++){*(p + i) = i;}//用完之后返回给堆区,以便下次的调用free(p);p = NULL;return 0;
}
如果越界访问,程序就会挂了,表现得现象就是程序窗口光标
2.3.对非动态开辟内存使用free释放
#include <stdio.h>
#include <stdlib.h>
int main()
{int a = 10;int *p = &a;free(p);p = NULL;return 0;
}
会报错的
2.4.使用free释放一块动态开辟内存的一部分
#include <stdio.h>
#include <stdlib.h>
int main()
{//申请动态内存空间int* p = (int*)malloc(100);//判断是否申请成功,并强制转换为整型if (p == NULL){perror("malloc");return 1;}//赋值int i = 0;for (i = 0; i < 25; i++){*p = i;p++;}//用完之后返回给堆区,以便下次的调用free(p);p = NULL;return 0;
}
2.5.对同一块动态内存多次释放
#include <stdio.h>
#include <stdlib.h>
int main()
{//申请动态内存空间int* p = (int*)malloc(100);//判断是否申请成功,并强制转换为整型if (p == NULL){perror("malloc");return 1;}free(p);//用完之后返回给堆区,以便下次的调用free(p);p = NULL;return 0;
}
2.6动态开辟内存忘记释放(内存泄漏)
#include <stdio.h>
#include <stdlib.h>
int main()
{int i = 1;int* p=&i;while (i--){p = (int*)malloc(20);}free(p);return 0;
}
上述的案例是申请了一次的内存块,电脑运行的内存几乎没有变化。
内存不释放的情况下,
#include <stdio.h>
#include <stdlib.h>
int main()
{while (1){malloc(20);}return 0;
}
运行上述程序之后,内存的会增大的,截取不到变化的之后的数值。
程序的设计限制了开辟内存空间的大小,如果不限制,程序一直运行下造成的内存泄漏,会导致计算机的性能下降,系统不稳定,资源的浪费等等。
2.7.总结
1.内存空间的开辟之后需要对返回的指针判断是否为NULL;
2.申请的内存空间要满足所存储数据的大小,不可访问越界;
3.内存申请函数和free函数配合使用,且释放之后将动态内存函数的返回值置为NULL;
4.不可释放多次内存空间;
5.动态内存使用完毕,一定要释放空间。