系列文章目录
系列文章:
能看懂文字就能明白系列
🌟 个人主页
:古德猫宁-
🌈 信念如阳光,照亮前行的每一步
文章目录
一、为什么要有动态内存
首先,我们知道这样的一个知识:所以程序都必须预留足够的来存储程序使用的数据。这些内存中有些是自动分配的。静态数据在程序载入内存时分配,而自动数据在程序执行块时分配,并在程序离开该块空间时销毁。
举个例子:
int x;//在栈空间上开辟四个字节
char arr[10] = { 0 };//在栈空间上开辟10个字节的连续空间
再比如我们想创建一个存储班级成绩的一个数组:
int main()
{
int n = 0;//
char ch = 'a';
//数组 - 存放一个班级的数学成绩
int arr[30] = {0};
return 0;
}
上述的开辟空间的方式由两个特点:
- 空间开辟大小是固定的。
- 数组在声明的时候,必须指定数组的长度,数组空间一旦确定了大小不能调整,但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,那数组的编译时开辟空间的方式就不能满足了。
- C语言引⼊了动态内存开辟,让程序员自己可以申请和释放空间,就比较灵活了。
二、mallloc和free的介绍和使用
malloc()函数
C语言提供了一个动态内存开辟的函数(malloc):
原型:
void* malloc (size_t size);
该函数接受一个参数:所需的内存字节数。
malloc()函数会找到合适的空闲内存块,这样的内存时匿名的。也就是说,malloc()分配内存,但是不会为其赋名,而是返回动态内存分配内存块的首字节地址。因此,我们可以把该地址赋给一个指针变量,并使用指针访问这块内存。
所以我们可以尝试用malloc()创建一个数组:
int* pst;
pst = (int*)malloc(10 * sizeof(int));
解释:以上代码为10个int类型的值请求空间,并用一个指针pst指向该位置。
注意:指针pst被声明为指向一个int类型,而不是指向内含10个int类型值的块,回忆一下,数组名是该数组首元素的地址。因此,如果让pst指向这个块的首元素,便可像使用数组名一样使用它。也就是说,可以使用表达式pst[0]访问该块的首元素,pst[1]访问第二个元素,以此类推。可以使用数组名来表示指针,也可以用指针来表示数组。
总结:
malloc这个函数向内存申请⼀块连续可用的空间,并返回指向这块空间的指针。
- 如果开辟成功,则返回⼀个指向开辟好空间的指针。
- 如果开辟失败,则返回⼀个 NULL 指针,因此malloc的返回值⼀定要做检查。
- 返回值的类型是 void* ,所以malloc函数只知道申请多大的空间,但是并不知道开辟空间的类型是什么,具体在使用的时候使用者自己来决定。
- 如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器
- malloc申请的空间是在内存的堆区
free()函数
C语言提供了另外一个函数free,专门用来做动态内存的释放和回收的,函数原型如下:
void free(void* ptr);
为什么要设计这样一个函数呢?
原因:
静态内存的数量在编译时是固定的,在程序运行期间也不会改变。自动变量使用的内存数量在程序执行期间自动增加或减少。但是动态内存的内存数量只会增加,除非用free()进行释放。
总结:
- 如果参数ptr指向的空间不是动态开辟的,那free函数的行为是未定义的。
- 如果参数ptr是NULL指针,则函数什么事都不做。
注意:malloc和free都声明在<stdlib.h>头文件中
malloc函数和free函数的结合使用
简单介绍完malloc和free,接下来尝试结合用一下
例如我们可以这样用:
#include <stdio.h>
#include <stdlib.h>
int main() {
int* ptr; // 声明一个指向整数的指针
int n = 5; // 假设要分配5个整数的空间
// 使用malloc分配n个整数大小的内存空间
ptr = (int*)malloc(n * sizeof(int));
if (ptr == NULL) { // 检查内存分配是否成功
printf("内存分配失败\n");//也可以用perror函数报错
return 1; // 返回错误代码
}
// 对分配的内存空间进行操作,例如赋值和打印
for (int i = 0; i < n; i++) {
ptr[i] = i + 1; // 给每个元素赋值
}
printf("分配的%d个整数的值为:\n", n);
for (int i = 0; i < n; i++) {
printf("%d ", ptr[i]); // 打印每个元素的值
}
printf("\n");
// 释放使用malloc分配的内存空间
free(ptr);
return 0;
}
总结:
通常,malloc()要与free()配套使用。free()函数的参数是之前malloc()返回的地址,该函数释放之前malloc()分配的内存。因此,动态分配内存的存储期从调用malloc()分配内存到调用free()释放内存为止。设想malloc()和free()管理着一个内存池。每次调用malloc()分配内存给程序使用,每次调用free()把内存归还内存池中,这样便可以重复使用这些内存。free()的参数应该是一个指针,指向由malloc()分配的一块内存。不能用free()释放通过其他方式(如,声明一个数组)分配的内存。
三、calloc()函数
C语言还提供了⼀个函数叫 calloc , calloc 函数也用来动态内存分配。原型如下:
void* calloc (size_t num, size_t size);
- 函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0。
- 与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。
举个例子:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int* p = (int*)calloc(10, sizeof(int));
if (NULL != p)
{
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", *(p + i));
}
}
free(p);
p = NULL;
return 0;
}
运行结果:
所以如果我们对申请的内存空间的内容要求初始化,那么可以很方便的使用calloc函数来完成任务
四、realloc()函数
realloc函数的出现让动态内存管理更加灵活
有时候我们发现申请的空间太小了,有时候我们又觉得申请的空间过大了,那为了合理的分配内存,我们一定会对内存的大小做灵活的调整。那realloc函数就可以做到对动态开辟内存大小的调整。
函数原型如下:
void* realloc(void* ptr,size_t size);
- ptr 是要调整的内存地址
- size调整之后新大小
- 返回值为调整之后的内存起始位置
- 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间。
- realloc如果调整空间失败,会返回NULL
- realloc如果调整内存空间成功存在两种情况:
情况1:原有空间之后有足够大的空间
情况2:原有空间之后没有足够大的空间
情况1
当是情况1的时候,要扩展内存就直接在原有内存之后直接追加空间,原来空间的数据不发生变化。
情况2
当是情况2的时候,原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另外找一个合适的大小的连续空间来使用。这样的函数返回的是一个新的内存地址。
由于上述的两种情况,realloc函数的使用就要注意一点。
#include <stdio.h>
#include <stdlib.h>
int main()
{
int* ptr = (int*)malloc(100);//申请空间
if (ptr != NULL)
{
//…………
}
else
{
return 1;
}
//扩展容量
//代码1 - 直接将realloc的返回值放到ptr中
ptr = (int*)realloc(ptr, 1000);//这样可以吗?(如果申请失败会如何?)
//代码2 - 先将realloc函数的返回值放在p中,不为NULL,在放ptr中
int* p = NULL;
p = realloc(ptr, 1000);//相当于创建了一个临时指针变量,不为空指针才赋值给原来的指针
if (p != NULL)
{
ptr = p;
}
//释放空间
free(ptr);
return 0;
}
总结
本文关于介绍动态内存分配的芝士暂时记录到这里,下一篇会介绍常用的动态内存错误。