C语言-动态内存分配讲解

目录

✨1.什么是动态内存分配

[💕2.动态内存开辟函数 malloc](#💕2.动态内存开辟函数 malloc)

✨3.malloc函数的检查(两种方法)

[💕4.动态内存释放函数 free](#💕4.动态内存释放函数 free)

[✨5.free 函数接收空指针](#✨5.free 函数接收空指针)

✨6.为什么要释放动态内存

💕7.动态内存开辟函数calloc

[💕8.动态内存调整函数 realloc](#💕8.动态内存调整函数 realloc)

✨9.动态内存分配------分配数组

[9.1 动态内存分配------一维数组](#9.1 动态内存分配——一维数组)

[9.2 动态内存分配------二维数组](#9.2 动态内存分配——二维数组)


春秋蝉在耳边鸣 无人与我并肩行

永生路上多坎坷 这路一人也走的


✨1.什么是动态内存分配

我们在处理内存时,内存区域主要分为三块区域,分别为:栈区,堆区,静态区

而我们经常使用的语句其实都是在栈区开辟空间的,如:

int arr[10];

char str[20];

struct S s[10];

int a;

float b;

char c;

但是使用这样的声明,在栈区开辟空间时是有一定风险的

存在风险:

  1. 开辟出来的空间是固定的,不能进行具体字节大小的修改,所以可能浪费掉栈区的空间大小

  2. 栈区空间开辟太大可能导致栈溢出

因此,我们可以将一些东西存储在堆区中,堆区的空间是本身就存在的,我们要做的就是分配出来并利用这些空间,因此也叫做动态内存分配

动态内存的分配是在堆区完成的,同时,堆区的内存空间几乎于无限大,因此不必担心如栈溢出的问题


动态内存分配是需要调用动态内存函数实现的,下面介绍四种内存函数

点击超链接可以进行访问函数

💕2.动态内存开辟函数 malloc

动态内存开辟函数访问链接 malloc

cpp 复制代码
#include<stdlib.h>
void* malloc(size_t size)
//以上是malloc函数的头文件以及参数

malloc 函数会返回一个void*类型的指针,这个指针是 malloc开辟空间大小的首地址

size表示空间大小是size个字节
动态内存函数 malloc 可以在堆区开辟出一定的空间大小供程序员使用

  1. malloc如果开辟成功,会返回所开辟空间的首地址

  2. malloc如果开辟失败,就会返回空指针(NULL指针)

我们无法确定malloc函数100%开辟成功,所以在利用malloc函数开辟空间时一定要进行检查

使用效果如下:

在使用malloc函数开辟空间时,因为malloc函数返回的地址是void*类型,因此我们需要根据自己的实际需要,进行地址类型的转换

如图:我们用malloc在堆区开辟了40个字节的内存空间大小,解引用p一次访问4个字节的大小

但我们会发现,我们使用malloc函数开辟的空间大小,没有解引用的部分都不会初始化值为0,返回全部都是cd,这是因为malloc函数在开辟空间大小时不会初始化内容


但我们这样使用malloc函数其实是不对的,之前我们强调过,malloc函数的使用一定要进行检查,否则编译器就会出现警告,如下图:

那么我们该如何解决呢?


✨3.malloc函数的检查(两种方法)

我们使用完malloc函数开辟内存后,因为无法100%确定开辟成功,所以我们需要对malloc函数是否开辟成功进行检查
检查的两种方法:

  1. 利用assert函数进行检查

  2. 使用if语句进行检查

1.assert情况检查

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#define NDEBUG
int main() {
	int* p = (int*)malloc(40);
	assert(p != NULL);
	*p = 20;
	printf("%d", *p);
}

malloc函数开辟失败就会返回空指针,因此只需要判断是否为空指针即可,如果为空指针,assert就会报错并停止运行代码(使用assert不用担心 NDEBUG 注释掉)

但使用assert有一点不好,如果我们再使用assert函数断言其他代码的同时,确定完malloc函数开辟成功后不能使用#define NDEBUG 注释掉assert函数,因为无法判断下一次执行代码malloc是否会开辟成功,这样就会影响代码的运行速度。可以使用assert函数,并不推荐用

2.if语句判断

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 
#include<stdio.h>
#include<stdlib.h>
int main() {
	int* p = (int*)malloc(40);
	if (p == NULL) {
		perror("malloc");
		return 1;
	}
	*p = 20;
	printf("%d", *p);
}

使用 if 语句,只需要判断是否为空指针即可,如果为空指针,我们可以使用perror函数打印出错误信息,并及时结束代码返回1,推荐使用 if 语句


💕4.动态内存释放函数 free

我们在堆区开辟完空间后,我们可以使用 free函数释放掉开辟的内存,并将这块内存空间还给操作系统

动态内存释放函数访问 free

cpp 复制代码
#include<stdlib.h>
void free(void* ptr)

free函数接收一个因动态内存函数所开辟内存空间的首地址

注意:

  1. free函数接受的地址一定是动态内存在堆区开辟空间的地址,不能用栈区的地址

2.free函数所接受的地址一定是动态内存开辟堆区地址的首地址

以上两种情况,如果实现任意一种,代码直接崩


free函数正确示例如下:

free 释放前的内存

free释放后的内存

注意:释放空间之后,free函数接收的指针(也就是因动态内存分配出的空间的首地址)就没有任何意义,但 free 不会自动给它置成空指针,此时他就成为一个野指针,所以我们要即及时将其置成空指针,如下:

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 
#include<stdio.h>
#include<stdlib.h>
int main() {
	int* p = (int*)malloc(40);

	if (p == NULL) {
		return 1;
	}

	free(p);
	p == NULL;
    //初始化为空指针
    //野指针在使用时是非常危险的,因为你不知道它指向哪里,所以要及时定义为空指针
}

✨5.free 函数接收空指针

free函数在接受空指针时会进行摆烂状态,什么也不做,如下:


✨6.为什么要释放动态内存

在我们利用动态内存分配出来的空间后,这些分配出来的空间只有两种方法可以释放掉

  1. 利用free函数,将动态内存分配出来的空间释放掉

  2. 等待程序自己结束,程序结束时会将这些空间释放掉

所以及时的释放掉堆区内存是非常重要的


💕7.动态内存开辟函数calloc

calloc函数与malloc函数都为开辟动态内存的函数,两者极其相似却有不同,接下来进行讲解

动态内存开辟函数访问 calloc

cpp 复制代码
#include<stdlib.h>
void* calloc (size_t num, size_t size);

calloc函数的第一个参数 num 为元素的个数

第二个参数 size 为每个元素字节的大小

calloc函数与malloc函数相同点:

如果开辟成功,会返回所开辟空间中较小空间的地址

如果开辟失败,就会返回空指针(NULL指针)

我们无法确定calloc函数100%开辟成功,所以在利用malloc函数开辟空间时一定要进行检查
calloc函数与malloc函数不同点

  1. 参数不同

  2. calloc函数在开辟完内存空间后,会将内存空间中每个字节全部初始化为0

效果如下:

可见,calloc与malloc函数相似,开辟的所有内存,每一个字节都会初始化为0

在free方面用法相同


💕8.动态内存调整函数 realloc

动态内存调整函数 realloc

虽然我们能通过 malloccalloc 函数动态内存分配一块空间,但这块空间分配完成后大小也是固定的,如果空间满了需要扩容或空间多了需要缩减,这时 realloc 函数就登场了

realloc 的作用是对已经动态分配的一块空间再次分配。

cpp 复制代码
#include<stdlib.h>
void* realloc (void* ptr, size_t size);

它有两个参数:

  1. ptr:要调整的内存地址,这块内存是动态内存分配得到的,必须是首地址,如果不是首地址代码运行就会崩
  2. size:以字节为单位的新大小

它会返回调整之后的内存起始位置。

关于调整之后的内存起始位置会出现以下两种情况:

  1. 与原来内存的起始位置相同
  2. 与原来内存的起始位置不同

当要缩小原有的内存时,说明原来的内存空间已经足够,此时它的返回值就是原来内存的起始位置。

当要扩大原有的内存时,又有两种情况:

  1. 原有空间之后有足够大的内存时,直接在原内存的基础上再开辟后边几个连续的空间,此时它的返回值是原来内存的起始位置;
  2. 原有空间之后没有足够大的内存进行扩容时,此时会在堆空间上另找一个合适大小的连续空间来使用,这样函数返回的是一个新的内存地址,与原来内存的起始位置不同。这时会拷贝原来空间的内容到新的空间的相应位置,原空间就被释放掉了。

虽然情况这么多,但其实很好更改与理解

所以,在使用 realloc 函数时就要注意用一个临时指针接收,当内存调整成功后再将临时指针赋给原指针

代码如下:

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 
#include<stdio.h>
#include<stdlib.h>
int main() {
	int* p = (int*)malloc(50);
	if (p == NULL) {
		return 1;
	}

	int * ptr = (int*)realloc(p, 30);

	if (ptr != NULL) {
		p = ptr;
	}

	free(p);
	p = NULL;
}

realloc函数调整前:

realloc函数调整后:

realloc函数在有一种情况下等价于malloc

代码如下:

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 
#include<stdio.h>
#include<stdlib.h>
int main() {
	int* p = (int *)realloc(NULL, 40);
	if (p == NULL) {
		return 1;
	}
	free(p);
	p = NULL;
}

这种情况下,就相当于开辟了40个字节的空间大小


✨9.动态内存分配------分配数组

9.1 动态内存分配------一维数组

利用动态内存分配出一维数组是较容易的,我们知道一维数组在内存中存储是连续的,我们可以利用这一点,动态内存分配出连续的空间,然后进行赋值利用,就实现了动态内存分配一维数组

代码如下:

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 
#include<stdio.h>
#include<stdlib.h>
int main() {
	int* p = (int*)malloc(40);

	if (p == NULL) {
		return 1;
	}

	for (int i = 0; i < 10; i++) {
		p[i] = i;
	}

	for (int i = 0; i < 10; i++) {
		printf("%d ", p[i]);
	}

	free(p);
	p = NULL;
}

可能不懂的点:

  1. p[ i ] = *(p+i)

9.2 动态内存分配------二维数组

使用动态内存分配出二维数组,我们有三种方法

第一种:利用二维数组其实是一堆一维数组的原理,开辟足够多的一维数组,并当作二维数组使用

cpp 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#define row 8
#define line 5
int main()
{
	
	int * p = (int*)malloc(row*line*sizeof(int));
	//或者int * p = (int*)malloc(160*sizeof(char));
    //开辟40个int类型的空间大小

	int count = 1;
	assert(p!=NULL);
	for(int i = 0;i<row;i++){

		for(int j = 0;j<line;j++){
			*(p+(i*line)+j) = count++;
		}
	}
	
		for(int i = 0;i<row;i++){
		for(int j = 0;j<line;j++){
			printf("%d ",	*(p+(i*line)+j));
		}
		printf("\n");
	}
	free(p);
	p = NULL;

}	

第二种:开辟4个 int* 类型的空间大小,让每一个 int* 都指向 5个int 的首地址

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
int main()
{
	int** p = (int**)malloc(4 * sizeof(int*));
	assert(p != NULL);
	for (int i = 0; i < 4; i++) {
		*(p + i) = (int*)malloc(5 * sizeof(int));
		assert(p + i != NULL);
	}
	int count = 1;
	for (int i = 0; i < 4; i++) {
		for (int j = 0; j < 5; j++) {
			assert((*(p + i)) + j != NULL);
			//*(*(p + i) + j * sizeof(char)) = count++;
			*((*(p + i)) + j) = count++;

		}
	}

	for (int i = 0; i < 4; i++) {
		for (int j = 0; j < 5; j++) {
			assert((*(p + i)) + j != NULL);
			//*(*(p + i) + j * sizeof(char)) = count++;
			printf("%d ", * ((*(p + i)) + j) );

		}
		printf("\n");
	}


	//释放每个int*所代表的5个int
	for (int i = 0; i < 4; i++) {
		free(*(p + i));
		*(p + i) = NULL;
	}

	//释放一级指针
	free(p);
	p = NULL;
}

这里一定要都释放,只释放 int* 的空间并不会释放 int 的空间

相关推荐
oe10195 分钟前
好文与笔记分享 A Survey of Context Engineering for Large Language Models(下)
人工智能·笔记·语言模型·agent
冷雨夜中漫步37 分钟前
高级系统架构师笔记——系统质量属性与架构评估(1)软件系统质量属性
笔记·架构·系统架构
czy878747538 分钟前
C语言实现状态模式
c语言·状态模式
czy87874751 小时前
C语言实现迭代器模式
c语言·迭代器模式
野生技术架构师1 小时前
牛客网Java 高频面试题总结(2025最新版)
java·开发语言·面试
一只鹿鹿鹿1 小时前
系统安全设计方案书(Word)
开发语言·人工智能·web安全·需求分析·软件系统
oe10191 小时前
好文与笔记分享 A Survey of Context Engineering for Large Language Models(中)
人工智能·笔记·语言模型·agent开发
GilgameshJSS1 小时前
STM32H743-ARM例程36-DNS
c语言·arm开发·stm32·单片机·嵌入式硬件
持梦远方1 小时前
【C++日志库】启程者团队开源:轻量级高性能VoyLog日志库完全指南
开发语言·c++·visual studio
聪明努力的积极向上1 小时前
【C#】HTTP中URL编码方式解析
开发语言·http·c#