动态内存管理||malloc和free.realloc和calloc

1.为什么要存在动态内存管理

int math[30]

像这种内存申请方式,一旦申请好空间,大小就无法调整

比如我只用26,浪费了16个字节

---》

能不能让程序员自己来动态的申请空间!

c/c++能

c语言就涉及到4个函数

malloc

free

calloc

realloc

2.malloc和free

2.1molloc

包含头文件《stdlib.h》

动态内存开辟函数

void* malloc(size_t size);

巷内村申请一块连续的空间,返回空间的起始地址

如果开辟成功,则返回一个只想开辟好空间的指针

如果开辟失败,则返回一个NULL的指针

返回值的类型是void*,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己决定

如果参数是size=0,malloc的行为是标准是未定义的 ,取决于编译器

cpp 复制代码
#include<stdlib.h>
int main()
{
	int* p = (int*)malloc(20);
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}

	int i = 0;
	for (i = 0; i < 5; i++)
	{
		*(p + i) = i + 1;

	}
	return 0;
}

这段代码是一个C 语言程序的主函数 ,核心功能是尝试向系统申请堆内存并检查申请是否成功,下面逐行详细解释:

  1. int main()

这是 C 程序的入口函数 ,程序从这里开始执行;int表示主函数执行后会返回一个整数(通常return 0代表程序正常结束,非 0 代表异常)。

  1. int* p = (int*)malloc(20);
  • malloc(20)malloc是 C 标准库函数(需包含头文件<stdlib.h>),作用是向系统的堆区申请 20 字节的连续内存空间

    • 如果申请成功,malloc返回这段内存的起始地址 (类型为void*,无类型指针);

    • 如果申请失败(比如内存不足),返回NULL(空指针)。

  • (int*):将malloc返回的void*强制转换为int*类型(指向int的指针),因为 C 语言不允许直接将void*赋值给其他类型指针(部分编译器可能兼容,但显式转换更规范)。

  • int* p:定义一个指向int的指针变量p,用来存储malloc返回的内存地址。

  1. if (p == NULL)

检查内存申请是否成功:

  • 如果p等于NULL,说明malloc申请内存失败(比如系统剩余内存不足);

  • 如果p不等于NULL,说明内存申请成功,p指向申请到的 20 字节内存的起始位置。

  1. perror("malloc"); return 1;
  • perror("malloc"):是 C 标准库函数(需包含头文件<stdio.h>),作用是打印错误原因 。它会先输出括号里的字符串(这里是malloc),然后自动补充系统的错误描述(比如内存不足时会输出malloc: Out of memory)。

  • return 1:表示程序异常退出(约定俗成用非 0 值表示执行失败)。

  1. return 0;

如果内存申请成功(p != NULL),程序执行到这里,返回0表示正常结束

补充说明

  • 这段代码没有释放申请的内存(需要用free(p);),会导致内存泄漏(程序结束后系统会自动回收,但良好的习惯是手动释放)。
  • 实际编译运行时,需要包含头文件:#include <stdio.h>#include <stdlib.h>

2.2free

专门用来做动态内存的释放,把空间的使用权限还给操作系统

void free(void* ptr);

void*指的是可以接受任意类型的地址

传空间的起始地址

如果传NULL,函数什么事不做

cpp 复制代码
#include<stdlib.h>
int main()
{
	int* p = (int*)malloc(20);
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}

	int i = 0;
	for (i = 0; i < 5; i++)
	{
		*(p + i) = i + 1;

	}

	free(p);
p=NULL;
	return 0;
}

所以free完了之后,p所指向的空间还给了操作系统,p但还指向那块空间,p成野指针

所以加一个p=NULL;避免成为野指针

cpp 复制代码
#include<stdlib.h>
int main()
{
	int* p = (int*)malloc(20);
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}

	int i = 0;
	for (i = 0; i < 5; i++)
	{
		*p = p + i;
		p++;

	}

	free(p);
p=NULL;
	return 0;
}

至于为什么不采用这种方式

因为p++;

后面free(p)会出现bug,p已经不是其实地址

3.calloc和 realloc

3.1calloc也用来进行动态内存分配

void* callloc(size_t num,size_t size);

功能就是为num大小为size的元素开辟空间,并且把空间的每个字节初始化为0(和malloc的区别)

3.2realloc

让内存动态管理更加灵活活

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

ptr是要调整的内存地址

size是调整后的大小

返回值为调整之后没存的起始地址

cpp 复制代码
#include<stdlib.h>
int main()

{
	int* p = (int*)malloc(5 * sizeof(int));
	if (p == NULL)
	{
		perror(malloc);
		return 1;

	}

	int i = 0;
	for (i; i < 5; i++)
	{
		*(p + i) = i + 1;

	}

	realloc(p, 40);
	return 0;
}

情况一二:

弱势请款2:

  1. 在堆区找一块新的空间,并且满足新的大小要求
  2. 在原来的空间的数据copy一份到新的空间
  3. 释放旧的空间
  4. 返回新的内存空间的起始地址

情况三:空间调整失败返回空指针

所以需要指针来接realloc

cpp 复制代码
#include<stdlib.h>
int main()

{
	int* p = (int*)malloc(5 * sizeof(int));
	if (p == NULL)
	{
		perror(malloc);
		return 1;

	}

	int i = 0;
	for (i; i < 5; i++)
	{
		*(p + i) = i + 1;

	}

	p=(int*)realloc(p, 40);
	return 0;
}

如果使用p来接受并不太靠谱

因为万一空间调整失败返回的是NULL;

p之前还存着20个空间的起始地址就是没了

int* ptr

cpp 复制代码
#include<stdlib.h>
int main()

{
	int* p = (int*)malloc(5 * sizeof(int));
	if (p == NULL)
	{
		perror(malloc);
		return 1;

	}

	int i = 0;
	for (i; i < 5; i++)
	{
		*(p + i) = i + 1;

	}

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

	if (ptr != NULL)
	{
		p = ptr;
	}
	else
	{
		perror("realloc");
	}
	//调整失败后p还是旧的空间
	return 0;
}

#include<stdlib.h>

int main()

{

int* p = (int*)malloc(5 * sizeof(int));

if (p == NULL)

{

perror(malloc);

return 1;

}

int i = 0;

for (i; i < 5; i++)

{

*(p + i) = i + 1;

}

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

if (ptr != NULL)

{

p = ptr;

int i = 0;

for (i = 5; i < 10; i++)

{

*(p + i) = i + 1;

}

for (i = 0; i < 10; i++)

{

printf("%d", *(p + i));

}

free(p);

p = NULL;

}

else

{

perror("realloc");

free(p);

p = NULL;

}

//调整失败后p还是旧的空间

return 0;

}

完整代码

realloc可以完成和malloc一样的功能

cpp 复制代码
int  main()
{
	realloc(NULL, 20);
	return 0;
}

4.常见动态内存管理的错误

4.1对NULl指针的解引用操作

cpp 复制代码
int  main()
{
	int* p = (int*)malloc(INT_MAX);
	*p = 20;
	return 0;
}

判断p是否为空指针

cpp 复制代码
int  main()
{
	int* p = (int*)malloc(INT_MAX);

	if (p == NULL)
	{
		perror("malloc");
		return 1;

	}
	*p = 20;


	return 0;
}

也可以用assert(p)

4.2对动态开劈空间的越界访问

cpp 复制代码
void test()
 {
 int i = 0;
 int *p = (int *)malloc(10*sizeof(int));
 if(NULL == p)
 {
 exit(EXIT_FAILURE);
 }
 for(i=0; i<=10; i++)
 {
 *(p+i) = i;//当i是10的时候越界访问
 }
 free(p);
 }

i<=10,11个了放生月结访问

4.3对⾮动态开辟内存使⽤free释放

cpp 复制代码
void test()
 {
int a = 10;
int *p = &a;
free(p);//ok?
}

4.4 使⽤free释放⼀块动态开辟内存的⼀部分

cpp 复制代码
void test()
{
int *p = (int *)malloc(100);
p++;
free(p);//p不再指向动态内存的起始位置
}

4.5 对同⼀块动态内存多次释放

cpp 复制代码
void test()
{
int *p = (int *)malloc(100);
free(p);
free(p);//重复释放
}
cpp 复制代码
void test()
{
int *p = (int *)malloc(100);
free(p);
p=NULL;
free(p);//重复释放
}

p=NULL可以避免

4.6 动态开辟内存忘记释放(内存泄漏)

cpp 复制代码
void test()
{
	int* p = (int*)malloc(100);
	if (NULL != p)
	{
		*p = 20;
	}

}
int main()
{
	test();
	while (1);

}

因为p的创建时临时变量,出了void后不释放,后面在main里也释放不了,造成内存泄露
忘记释放不再使⽤的动态开辟的空间会造成内存泄漏。
切记:动态开辟的空间⼀定要释放,并且正确释放。

内存是一种资源,申请后不用记得回收

其实mallloc和calloc/realloc申请的内存,如果不想使用的话,可以使用free释放

如果没有使用free来释放,当程序运行结束后,也会有操作系统回收

但是如果程序一直嗯不退出,空间就会一直站着

尽量做到谁(函数)申请的空间谁释放,如果不能释放,要告诉使用的人记得释放

那么要做到malloc/free成对出现

5.动态内存经典笔试题的分析

5.1

cpp 复制代码
void GetMemory(char* p)
{
	p = (char*)malloc(100);
}
//P是形参,出来之后p销毁,空间还给操作系统
void Test(void)
{
	char* str = NULL;
	GetMemory(str);
	strcpy(str, "hello world");
	printf(str);
}

int main()
{
	Test();
	return 0;
}

//1.内存泄漏
//2.程序崩溃

1.str传值调用给getmemory,用一个char*接受,则p=NULL

2.malloc开辟100个字节的空间,p只想该控件的首字节的地址

3.但是出了void后p销毁,申请的100字节空间还在,但只有p知道该控件的位置

4.str还是NULL

5.strcpy肯定要对str解引用,NULL解引用,程序崩溃

想要修改的话

方式1

cpp 复制代码
void GetMemory(char** p)
{
	*p = (char*)malloc(100);
}
//P是形参,出来之后p销毁,空间还给操作系统
void Test(void)
{
	char* str = NULL;
	GetMemory(&str);
	strcpy(str, "hello world");
	printf(str);
	free(str);
	str = NULL;
}

int main()
{
	Test();
	return 0;
}

//1.内存泄漏
//2.程序崩溃

str传地址调用;

使用char**二级指针来接

则可以改变str;

后free释放

图示:

原来:p是指向str的

后来:str换成100的地址

方式2

cpp 复制代码
char* GetMemory(char* p)
{
	*p = (char*)malloc(100);
	return p;
}
//P是形参,出来之后p销毁,空间还给操作系统
void Test(void)
{
	char* str = NULL;
	str=GetMemory(str);
	strcpy(str, "hello world");
	printf(str);
	free(str);
	str = NULL;
}

int main()
{
	Test();
	return 0;
}

//1.内存泄漏
//2.程序崩溃

返回p,同时getmemory也可以不传参。

cpp 复制代码
char* GetMemory()
{
	char* p = (char*)malloc(100);
	return p;
}
//P是形参,出来之后p销毁,空间还给操作系统
void Test(void)
{
	char* str = NULL;
	str=GetMemory();
	strcpy(str, "hello world");
	printf(str);
	free(str);
	str = NULL;
}

int main()
{
	Test();
	return 0;
}

5.2

cpp 复制代码
char* GetMemory(void)
{
	char p[] = "hello world";
	return p;
}
void Test(void)
{
	char* str = NULL;
	str = GetMemory();
	printf(str);
}

打印不出也可能打印出

因为p是函数的局部变量,出函数后空间还给操作系统,返回的p是野指针,使用str指针取放位p数组属于非法访问

cpp 复制代码
int* test()
{
	int n = 10;
	return &n;

}

int main()
{
	int* p = test();

	return 0;
}

也是返回临时变量的地址的问题,

但局部变量的内容是可以传的

5.3

cpp 复制代码
void GetMemory(char** p, int num)
{
	*p = (char*)malloc(num);
}
void Test(void)
{
	char* str = NULL;
	GetMemory(&str, 100);
	strcpy(str, "hello");
	printf(str);
free(str);
str=MULL;
}

int main()
{
	Test();

	return 0;
}

代码一气呵成,唯一的问题就是忘记free,加上就行

5.4

cpp 复制代码
void Test(void)
 {
 char *str = (char *) malloc(100);
 strcpy(str, "hello");
 free(str);
 if(str != NULL)
 {
 strcpy(str, "world");
 printf(str);
 }
 }

6.柔性数组

1.建立在结构体中,最后一个成员

2.最后一个成员是数组,数组没有指定大小

这个数组才是柔性数组

cpp 复制代码
struct s
{
	char c;
	int arr[];
};

有一些编译器支持这种

cpp 复制代码
struct s
{
	char c;
	int arr[0];
};

放0下去也是没知名大小

特点

柔性数组前面得有至少一个成员;

sizeof返回的这种结构大小不包括柔性数组的内存,因为其大小没指定

cpp 复制代码
struct s
{
	char c;
	int arr[];
};
int main()
{
	printf("%zd", sizeof(struct s));
	return 0;
}

包含柔性数组成员的结构⽤malloc ()函数进⾏内存的动态分配,并且分配的内存应该⼤于结构的⼤
⼩,以适应柔性数组的预期⼤⼩。

后面的arr也可以用realloc进行柔性调整;

cpp 复制代码
struct s
{
	char c;
	int arr[];
};
int main()
{
	struct s* pf=(struct s*)malloc(sizeof(struct s) + 5 * sizeof(int));
	if (pf = NULL)
	{
		perror(malloc);
		return 1;
	}
	pf->c = 'a';
	int i = 0;
	for (i = 0; i < 5; i++)
	{

		pf->arr[i] = i;
	}
	//调整空间
	struct s* ptr=(struct s*)realloc(pf, sizeof(struct s)+10 * sizeof(int));
	if (ptr != NULL)
	{
		pf = ptr;

	}
	//释放空间
	free(pf);
	pf = NULL;
	return 0;
}
	return 0;
}

另外一种方案:

cpp 复制代码
struct s
{
	int n;
	int* arr;

};

int main()
{
	struct s* ps=(struct s*)malloc(sizeof(struct s));
	if (ps == NULL)
	{
		perror(malloc);
		return 1;
	}
	ps->arr =(int*) malloc(5 * sizeof(int));

	if (ps->arr == NULL)
		return 1;
	//使用

	ps->n = 100;
	int i = 0;
	for (i; i < 5; i++)
	{
		ps->arr[i] = i;

	}

	//调整数组的大小
	int* ptr = (int*)realloc(ps->arr, 10 * sizeof(int));
	if (ptr != NULL)
	{
		ps->arr = ptr;
	
	}
	//使用

	//释放

	free(ps->arr);//先释放arr,因为先释放ps的话就找不到arr了
	free(ps);
	return  0;
}

显然第一种使用柔性数组的方案更加
第⼀个好处是:⽅便内存释放
如果我们的代码是在⼀个给别⼈⽤的函数中,你在⾥⾯做了⼆次内存分配,并把整个结构体返回给⽤
⼾。⽤⼾调⽤free可以释放结构体,但是⽤⼾并不知道这个结构体内的成员也需要free,所以你不能
指望⽤⼾来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存⼀次性分配好了,并返
回给⽤⼾⼀个结构体指针,⽤⼾做⼀次free就可以把所有的内存也给释放掉。
第⼆个好处是:这样有利于访问速度.
连续的内存有益于提⾼访问速度,也有益于减少内存碎⽚。(其实,我个⼈觉得也没多⾼了,反正你
跑不了要⽤做偏移量的加法来寻址)

相关推荐
YA3331 小时前
mcp-grafana mcp 使用stdio报错
java·开发语言
周杰伦_Jay1 小时前
【Go 语言主流 Web】 框架详细解析
开发语言·后端·微服务·架构·golang
数字化脑洞实验室1 小时前
智能决策与决策优化:从算法到产业的演进逻辑
算法
cpp_25011 小时前
P5412 [YNOI2019] 排队
数据结构·c++·算法·题解·洛谷
kingmax542120081 小时前
图论核心算法(C++):包括存储结构、核心思路、速记口诀以及学习方法, 一站式上机考试学习【附PKU百练,相关练习题单】
c++·算法·图论·信奥赛·上机考试·百练·pku
罗湖老棍子1 小时前
【例9.15】潜水员(信息学奥赛一本通- P1271)
c++·算法·动态规划·二维费用背包
_OP_CHEN1 小时前
算法基础篇:(二十一)数据结构之单调栈:从原理到实战,玩转高效解题
数据结构·算法·蓝桥杯·单调栈·算法竞赛·acm/icpc
PfCoder1 小时前
WinForm真入门(20)——StatusStrip控件解析
开发语言·windows·c#·winform·statusstrip
灵犀坠2 小时前
前端面试八股复习心得
开发语言·前端·javascript