C语言详解(动态内存管理)1

Hi~!这里是奋斗的小羊,很荣幸您能阅读我的文章,诚请评论指点,欢迎欢迎 ~~
💥💥个人主页:奋斗的小羊
💥💥所属专栏:C语言


🚀本系列文章为个人学习笔记,在这里撰写成文一为巩固知识,二为展示我的学习过程及理解。文笔、排版拙劣,望见谅。


目录

  • 前言
  • 1、为什么要有动态内存分配
  • [2、malloc 和 free](#2、malloc 和 free)
    • [2.1 malloc](#2.1 malloc)
    • [2.2 free](#2.2 free)
  • [3、calloc 和 realloc](#3、calloc 和 realloc)
    • [3.1 calloc](#3.1 calloc)
    • [3.2 realloc](#3.2 realloc)
  • 总结

前言

本篇文章将介绍C语言中除指针和结构体外又一重要的内容------动态内存管理

在C语言中,我们更多的需要手动分配和释放内存,这意味着我们必须正确地管理内存,以避免内存泄漏、内存溢出和其他内存错误,这些错误可能导致程序崩溃或安全漏洞。因此,了解内存管理是编写高质量、高效率和健壮性程序的重要部分。


1、为什么要有动态内存分配

目前我们申请内存的方法有两种,创建相关类型变量int n = 0;和创建相关类型数组int arr[10] = { 0 };

但是这样申请的内存是有缺点的:

  • 申请的内存大小是有限的,不能指定大小

  • 数组在声明的时候必须指定长度,数组空间一旦确定下来就不能调整

  • 数组空间在申请前我们不能给出一个准确的大小,大了浪费,小了不够

有时候我们需要的空间大小在程序运行的时候才能知道,那数组的编译时开辟空间的方式就不能满足了

为了解决这个问题,C语言引入了动态内存开辟,让我们可以自己申请和释放内存,这样就比较灵活了

空间不够我们可以增大,空间太大我们可以缩小


2、malloc 和 free

使用动态内存管理函数都需要包含头文件<stdlib.h>

2.1 malloc

C语言提供了一个动态内存开辟的函数malloc

c 复制代码
void* malloc(size_t size);

malloc 函数的作用是开辟一块指定大小的、连续的、有限的内存空间,大小由size 决定,是不能开辟无限空间的
在x86环境下开辟一块超大内存空间,若开辟失败打印出失败原因:

c 复制代码
#include <stdio.h>
#include <stdlib.h>

int main()
{
	int* p = (int*)malloc(INT_MAX);//INT_MAX=2147483647
	if (p == NULL)
	{
		//空间开辟失败
		perror("malloc");
		//失败后用return终止程序
		return 1;
	}
	return 0;
}

对于malloc函数,我们需要注意:

  • 参数的单位是字节
  • 如果size是0,malloc的行为是未定义的,取决于编译器
  • malloc的返回值是void *类型的指针
  • 申请空间成功的话返回起始地址,反之则返回NULL
  • malloc返回的地址我们基本都会直接强转为我们需要的类型的地址

示例:申请10个整形空间,存入1~10

c 复制代码
#include <stdio.h>
#include <stdlib.h>

int main()
{
	int* p = (int*)malloc(10 * sizeof(int));
	if (p == NULL)
	{
		//空间开辟失败
		perror("malloc");
		//失败后用return终止程序
		return 1;
	}
	//可以使用开辟好的空间
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i + 1;
	}
	return 0;
}

malloc申请的空间和数组有什么区别?

  • 动态内存的大小可以调整
  • 空间开辟的位置不一样

我们创建的局部数组就在栈区

虽然空间有区别,但在使用上是一样的


2.2 free

C语言提供了另外一个函数free,专门是用来做动态内存的释放和回收的,mallocfree基本都要成对存在,函数原型如下:

c 复制代码
void free(void* ptr);

free函数是用来释放 开辟的动态内存的,我们将上面开辟的动态内存释放:

c 复制代码
#include <stdio.h>
#include <stdlib.h>

int main()
{
	int* p = (int*)malloc(10 * sizeof(int));
	if (p == NULL)
	{
		//空间开辟失败
		perror("malloc");
		//失败后用return终止程序
		return 1;
	}
	//可以使用开辟好的空间
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i + 1;
	}
	//将开辟的动态内存释放
	free(p);
	p = NULL;
	return 0;
}

注意 :用free释放动态内存空间后,指针p中还保留着其地址,安全起见我们需要给指针p赋NULL,因此free(p)p = NULL总是一起出现的

既然有free函数,所以说明动态内存是不能自动回收的,所以malloc申请的空间和数组又有了一个区别:

数组在进它的作用域时申请空间,出作用域时自动释放空间;而malloc申请的动态内存空间需要我们手动地释放

如果不释放,程序结束的时候也会被系统自动回收,但是并不建议这样做,自己申请的空间要自己释放,不然会浪费资源,也是不负责任的行为

特别的:

  • 如果参数ptr指向的空间不是动态开辟的,free的行为是未定义的
  • 如果参数ptrNULL指针,则free什么都不做

3、calloc 和 realloc

3.1 calloc

C语言还提供了一个函数calloc,其函数原型是:

c 复制代码
void* calloc( size_t num, size_t size );

calloc的作用是开辟num个大小为size的连续空间,同时将内存空间初始化为0

calloc申请10个整型的空间,并打印出内存中的值

c 复制代码
#include <stdio.h>
#include <stdlib.h>

int main()
{
	//int* p = (int*)malloc(10 * sizeof(int));
	int* p = (int*)calloc(10, sizeof(int));
	if (p == NULL)
	{
		//空间开辟失败
		perror("calloc");
		//失败后用return终止程序
		return 1;
	}
	//可以使用开辟好的空间
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", p[i]);//*(p + i)
	}
	//将开辟的动态内存释放
	free(p);
	p = NULL;
	return 0;
}

如果将malloc申请的动态内存空间中的值打印出来,应该都是随机值:

所以malloccalloc只两个区别:

  • malloc有1个参数,而calloc有2个参数
  • calloc会把申请的动态内存空间内的值初始化为全0,而malloc不会

3.2 realloc

在文章开头我们提到了,有时在定义数组的时候我们并不能给定数组一个准确的长度,大了浪费,小了不够。

realloc函数的出现让动态内存管理更加灵活,它的作用是调整动态内存空间的大小,原型如下:

c 复制代码
void *realloc( void *ptr, size_t new_size );
  • ptr:指向之前通过malloccallocrealloc开辟的内存块(必须是起始地址)
  • new_size:内存新大小(单位字节)
  • 返回值void *:调整后的内存起始地址,若失败则返回空指针

当我们想用realloc函数将一个动态内存空间调整的小一点,则相应的动态内存空间就会减小到我们想要的大小;而当我们想用realloc函数将一个动态内存空间调整的大一点,这时候就会有两种情况出现:

情况一 :原内存后的可用空间足够我们的扩容

这时候realloc函数就会按正常程序走,返回原内存的起始地址

情况二 :原内存后的可用空间不够我们扩容

这时候realloc函数会在堆区 中找一块足以完成我们目的的内存空间,并将原内存中的内容拷贝到新内存空间中,realloc函数还会自己将原内存空间释放 ,最后返回新开辟的内存空间的起始地址

当然,不管我们是想将原内存空间调小还是扩容,都有失败的可能

所以,realloc函数的返回值我们不能直接用指向原内存的指针接收,因为如果realloc返回的是NULL,则原内存的地址都会消失

我们可以用一个新指针过渡

c 复制代码
#include <stdio.h>
#include <stdlib.h>

int main()
{
	//int* p = (int*)malloc(10 * sizeof(int));
	int* p = (int*)calloc(10, sizeof(int));
	if (p == NULL)
	{
		//空间开辟失败
		perror("calloc");
		//失败后用return终止程序
		return 1;
	}
	//可以使用开辟好的空间
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", p[i]);//*(p + i)
	}
	//调整空间,扩容到20个整型空间
	int* ptr = (int*)realloc(p, 20 * sizeof(int));//用新指针过渡
	if (ptr != NULL)
	{
		p = ptr;
	}
	//使用
	// ...
	
	//将开辟的动态内存释放
	free(p);
	p = NULL;
	return 0;
}

总结

  • 动态内存管理通过使用malloccallocrealloc等函数来分配内存,使用free函数来释放已经分配的内存。
  • 动态内存管理能够优化程序的内存利用率,避免内存泄漏和内存溢出等问题,在C语言中,动态内存管理是我们必须掌握的重要技能之一
相关推荐
史不了1 小时前
静态交叉编译rust程序
开发语言·后端·rust
ad钙奶长高高1 小时前
【C语言】扫雷游戏详解
c语言
读研的武1 小时前
DashGo零基础入门 纯Python的管理系统搭建
开发语言·python
Andy2 小时前
Python基础语法4
开发语言·python
但要及时清醒2 小时前
ArrayList和LinkedList
java·开发语言
孚亭2 小时前
Swift添加字体到项目中
开发语言·ios·swift
hweiyu002 小时前
Go、DevOps运维开发实战(视频教程)
开发语言·golang·运维开发
mm-q29152227292 小时前
Python+Requests零基础系统掌握接口自动化测试
开发语言·python
星星火柴9363 小时前
笔记 | C++面向对象高级开发
开发语言·c++·笔记·学习