目录
一般开辟内存的方式有两个:
int a = 20;//创建变量---在栈空间上开辟4个字节
char arr[10] = {0};//创建数组---在栈空间上开辟10个字节的连续空间
但是这样开辟空间的大小是固定的,而且数组在申明时必须指定数组的长度,后续也不能调整大小。
事实上,我们对空间的需求有时候是具有灵活性的,远不止上述这两种情况。
c语言引入了动态内存开辟,让程序员可以自己申请和释放空间。
1. 动态内存开辟函数
动态内存开辟相关的函数有这四个:malloc,free,calloc,realloc。
他们都声明在stdlib.h头文件中。
我们逐一学习。
1.1 malloc函数
malloc函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。
如果开辟成功,则返回一个指向开辟好空间的指针。
如果开辟失败,则返回一个 NULL 指针,因此malloc的返回值一定要做检查。
返回值的类型是 void*,所以malloc函数并不知道开辟空间的类型,具体在使用的时候程序员自己来决定。记得根据所需类型强制类型转换。
参数类型是size_t,是想要开辟的内存空间大小,单位是字节。如果参数 size为0,malloc的行为是未定义的,取决于编译器。
操作一下:
//假设我们想要开辟的内存空间类型是int,5个整型的大小。
int* ptr = ()malloc(sizeof(int)*5);
1.2 free函数
free函数是专门用来做动态内存的释放和回收的。
无返回类型。
参数ptr是指针类型,如果ptr指向的空间不是动态开辟的,那free函数的行为是未定义的。
如果ptr是NULL指针,则函数什么都不做。
操作一下:
int main()
{
int num = 0;
scanf("%d", &num);
//使用数组来申请内存,c语言会报错。
//int arr[num] = { 0 };
//使用动态内存的函数malloc
//动态申请内存
int* ptr = NULL;
ptr =(int*) malloc(sizeof(int) * num);
if (ptr != NULL)//判断ptr是否是空指针
{
int i = 0;
for (i = 0; i < num; i++)
{
*(ptr + i) = i;
printf("%d ", *(ptr + i));
}
}
//释放内存
free(ptr);
ptr = NULL;
return 0;
}
运行结果:
1.3 calloc函数
calloc函数也用来动态内存分配。
calloc函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0。
返回类型和malloc函数一模一样,取决于程序员自己,记得强制类型转换。
calloc函数与malloc函数的区别只在于 calloc 函数会在返回地址之前把申请的空间的每个字节初始化为0。
如果我们对申请内存空间的内容要求初始化,那我们可以很方便地使用calloc函数来实现代码~
操作一下:
代码:
int main()
{
//申请内存
int* ptr = (int*)calloc( 5 , sizeof(int));
if (ptr != NULL)
{
for (int i = 0; i < 5; i++)
{
printf("%d ", *(ptr + i));//看看calloc函数是否会初始化
}
}
//释放内存
free(ptr);
ptr = NULL;
return 0;
}
结果:
1.4 realloc函数
realloc函数的出现让动态内存管理更加灵活。
有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了。那为了合理的使用内存,我们一定会对内存的大小做灵活的调整。
realloc函数就可以做到对动态开辟内存大小的灵活调整。
第一个参数ptr是要调整的内存地址。
第二个参数size是调整之后内存的新大小。
返回值是调整之后内存的起始位置。
注意,我们使用realloc函数进行调整内存空间时,会存在三种情况:
情况1:原有空间之后有足够大的空间
此时要扩展内存,就直接在原有内存空间之后追加空间即可,原有空间的数据不发生变化。
情况2:原有空间之后没有足够大的空间
此时,内存扩展的方法是:在堆空间上另找一个大小合适的连续空间来使用,然后会将原来空间的数据拷贝一份到新的空间,释放旧的空间,最后返回新的内存空间的起始地址。
情况3:调整失败
函数此时返回空指针。
在实际使用realloc函数时,要小心注意一些。
我们操作一下:
int main()
{
//申请内存
int* ptr = (int*)malloc(20);
assert(ptr);
//扩展内存
//代码1不推荐
ptr = (int*)realloc(ptr, 40);
//更推荐代码2
int* p = (int*)realloc(ptr, 40);
if (p != NULL)
{
ptr = p;
}
else
{
return 1;
}
//释放内存
free(ptr);
ptr = NULL;
p = NULL;
return 0;
}
realloc函数可以完成和malloc函数一样的功能:
//realloc函数可以完成和malloc函数一样的功能
int main()
{
realloc(NULL, sizeof(int)*5);
//等同于int* ptr=(int*)malloc(sizeof(int) * 5);
return 0;
}
2. 常见的一些动态内存的错误
罗列一下,程序员使用过程中要小心使用:
1)对NULL指针解引用操作
2)对动态开辟空间的越界访问
3)对非动态开辟内存使用free函数释放
4)使用free函数释放一块动态开辟内存的一部分
5)对同一块动态内存多次释放
6)动态开辟内存忘记释放(内存泄漏)
3.c/c++中程序内存区域划分
简单讲一下c/c++中程序内存区域的划分,程序员在学习中会有更深的理解。
1)栈区(stack):
在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。
2)堆区(heap):
一般由程序员分配释放,若程序员不释放,程序结束时可能由0S回收。分配方式类似于链表。
3)静态区(数据段):
(static)存放全局变量、静态数据。程序结束后由系统释放。
4)代码段:
存放函数体(类成员函数和全局函数)的二进制代码。