C语言动态内存分配详解(能看懂文字就能明白系列)

系列文章目录

系列文章:
能看懂文字就能明白系列
个人主页:古德猫宁-

信念如阳光,照亮前行的每一步


文章目录

  • 系列文章目录
    • *信念如阳光,照亮前行的每一步*
  • 一、为什么要有动态内存
  • 二、mallloc和free的介绍和使用
    • malloc()函数
    • free()函数
    • malloc函数和free函数的结合使用
  • 三、calloc()函数
  • 四、realloc()函数
  • 总结


一、为什么要有动态内存

首先,我们知道这样的一个知识:所以程序都必须预留足够的来存储程序使用的数据。这些内存中有些是自动分配的。静态数据在程序载入内存时分配,而自动数据在程序执行块时分配,并在程序离开该块空间时销毁。

举个例子:

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都声明在头文件中

malloc函数和free函数的结合使用

简单介绍完malloc和free,接下来尝试结合用一下

例如我们可以这样用:

#include 
#include 
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 
#include 
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;
}

运行结果:
C语言动态内存分配详解(能看懂文字就能明白系列)_第1张图片

所以如果我们对申请的内存空间的内容要求初始化,那么可以很方便的使用calloc函数来完成任务

四、realloc()函数

realloc函数的出现让动态内存管理更加灵活
有时候我们发现申请的空间太小了,有时候我们又觉得申请的空间过大了,那为了合理的分配内存,我们一定会对内存的大小做灵活的调整。那realloc函数就可以做到对动态开辟内存大小的调整。
函数原型如下:

void* realloc(void* ptr,size_t size);

  • ptr 是要调整的内存地址
  • size调整之后新大小
  • 返回值为调整之后的内存起始位置
  • 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间。
  • realloc如果调整空间失败,会返回NULL
  • realloc如果调整内存空间成功存在两种情况:
    情况1:原有空间之后有足够大的空间
    情况2:原有空间之后没有足够大的空间
    C语言动态内存分配详解(能看懂文字就能明白系列)_第2张图片

情况1
当是情况1的时候,要扩展内存就直接在原有内存之后直接追加空间,原来空间的数据不发生变化。

情况2
当是情况2的时候,原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另外找一个合适的大小的连续空间来使用。这样的函数返回的是一个新的内存地址。

由于上述的两种情况,realloc函数的使用就要注意一点。

#include 
#include 
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;
}

总结

本文关于介绍动态内存分配的芝士暂时记录到这里,下一篇会介绍常用的动态内存错误。

你可能感兴趣的:(C语言笔记,c语言,开发语言,学习,笔记,经验分享)