首页 > 系统相关 >动态内存分配

动态内存分配

时间:2024-11-04 22:47:57浏览次数:5  
标签:10 NULL str int 动态内存 空间 include 分配

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

在这里插入图片描述

二、malloc和free

在这里插入图片描述
栈区中的数据出了作用域就会销毁;而静态区中数据的生命周期与全局变量一致,出了作用域也不会被销毁,直至程序结束后才会销毁。

malloc函数与free函数需要包含的头文件是<stdlib.h>

①malloc

在这里插入图片描述

②free

在这里插入图片描述

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
int main()
{
	//开辟10个整型的空间
	int* p = (int*)malloc(10 * sizeof(int));
	//p的值是开辟的10个整型空间中,第一个整型空间的地址
	assert(p != NULL);
	for (int i = 0; i < 10; i++)
	{
		*(p + i) = 1 + i;
	}
	return 0;
	//释放空间
	//free将申请的10个整型的空间释放后,p中依然存放的是原来申请的10个整型中,第一个整型空间的地址,此时p就是野指针
	free(p);
	p=NULL;
	//如果不用free函数手动释放这块空间的话,等程序运行结束后,这块空间也会自动被操作系统回收。
	
}

二、calloc与realloc

使用calloc与realloc函数需要包含的头文件是<stdlib.h>

①calloc

在这里插入图片描述

#include<stdlib.h>
#include<assert.h>
#include<stdio.h>
int main()
{
	int* p = (int*)calloc(10, sizeof(int));
	//向内存申请10个整型空间,并将每个字节初始化为0
	//p中存放的是第一个整型空间的地址
	assert(p != NULL);
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", p[i]);//p[i]等价于*(p+i)
	}
    //释放空间
	free(p);
	//free函数将这10个整型空间回收后,p中存放的依旧是原先第一个整型空间的地址,此时p就是野指针
	p = NULL;
	return 0;
}

②malloc与calloc的区别

a.malloc函数只有一个参数,而calloc函数有两个参数
b.calloc函数会将动态开辟的每个字节初始化为0,而malloc函数动态开辟的每个字节都是随机值。

③realloc

在这里插入图片描述

a.realloc函数的介绍

有时会我们发现原先动态开辟的空间太小了(或者太大了),那为了合理的使用内存,我们需要对该内存的大小做灵活的调整。那么 realloc 函数就可以调整动态开辟的内存大小。

b.realloc函数调整动态开辟的空间时的两种情况:

情况1:原有空间之后有足够大的空间时,若想扩展空间的话,就直接在原有空间之后直接扩展,且原来空间的数据不发生变化。

#include<stdlib.h>
#include<assert.h>
#include<stdio.h>
int main()
{
	int* p = (int*)calloc(10, sizeof(int));
	//向内存申请10个整型空间,并将每个字节初始化为0
	//p中存放的是第一个整型空间的地址
	assert(p != NULL);
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", p[i]);//p[i]等价与*(p+i)
	}

	//将原先动态开辟的10个整型空间扩展为12个整型空间
	int* ptr = (int*)realloc(p, 12 * sizeof(int));
	//用一个新变量ptr来接收新的空间的起始地址的目的:如果用p来接收的话,倘若扩展空间失败了,则p的值变为NULL了,此时原来扩展的10个整型空间就找不到了。
	assert(ptr != NULL);
	p = ptr;
	//释放空间
	free(p);
	p = NULL;
	ptr = NULL;
	return 0;
}

在这里插入图片描述
情况2:原有空间之后没有足够大空间,扩展的方法是:在堆区上另找⼀个大小合适
的连续空间来使用,并将旧的数据拷贝到新的空间,然后回收旧的空间。此时realloc函数返回的是⼀个新的内存地址。

#include<stdlib.h>
#include<assert.h>
#include<stdio.h>
int main()
{
	int* p = (int*)calloc(10, sizeof(int));
	//向内存申请10个整型空间,并将每个字节初始化为0
	//p中存放的是第一个整型空间的地址
	assert(p != NULL);
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", p[i]);//p[i]等价与*(p+i)
	}

	//将原先动态开辟的10个整型空间扩展为50个整型空间
	int* ptr = (int*)realloc(p, 50 * sizeof(int));
	//用一个新变量ptr来接收新的空间的起始地址的目的:如果用p来接收的话,倘若扩展空间失败了,则p的值为NULL,此时原来扩展的10个整型空间就找不到了。
	assert(ptr != NULL);
	p = ptr;
	//释放空间
	free(p);
	p = NULL;
	ptr = NULL;
	return 0;
}

在这里插入图片描述

④realloc除了可以调整动态开辟空间的大小外,还可以动态开辟空间

#include<stdlib.h>
#include<assert.h>
int main()
{
	int* p = (int*)realloc(NULL, 10 * sizeof(int));
	//等价于int* p = (int*)malloc(10 * sizeof(int));
	//在堆区申请了10个连续整型的空间
	assert(p != NULL);

	//使用空间
	//......

	//回收空间
	free(p);
	p = NULL;
	return 0;
}

三、动态内存分配常见的错误

①对空指针解引用

#include<stdlib.h>
int main()
{
	int* p = (int*)malloc(10 * sizeof(int));
	*p = 20;//p如果是空指针,编译器就会报错
	return 0;

②对动态开辟的空间造成了越界访问

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
int main()
{
	//开辟10个整型的空间
	int* p = (int*)malloc(10 * sizeof(int));
	assert(p != NULL);
	for (int i = 0; i < 40; i++)
	{
		*(p + i) = 1 + i;
	}
	//只动态开辟了10个整型空间,却访问了40个整型的空间,造成了越界访问。
	return 0;
	//释放空间
	free(p);
	p=NULL;
	
}

③对非动态开辟的空间使用free来回收

#include<stdlib.h>
int main()
{
	
	int arr[10] = { 0 };
	free(arr);
	return 0;
}

④使用free函数回收动态开辟空间的一部分

#include<stdlib.h>
#include<assert.h>
int main()
{
	int* p = (int*)malloc(10 * sizeof(int));
	assert(p != NULL);
	for (int i = 0; i < 5; i++)
	{
		*p = i;
		p++;
	}
	free(p);
	//此时p指向的不是动态开辟空间的起始位置,编译器将会报错
	p = NULL;
	return 0;
}

⑤动态开辟的空间忘记回收(造成内存泄漏)

void test()
{
	int* p = (int*)malloc(100);
	if (p != NULL)
	{
		*p = 20;
	}
}
int main()
{
	test();
	//......
	return 0;
}
//直至程序执行到return 0 前,动态开辟的10个整型空间不会被回收(当动态开辟的空间不再使用时,应该及时将其回收)

四、动态内存分配经典笔试题

补充:将常量字符串直接传给printf函数时,printf本质上接收的是常量字符串中首字符的地址

#include<stdio.h>
int main()
{
	printf("abcdef\n");//printf本质上接收的是字符串首字符的地址
	char* p = "abcdef";
	printf(p);
	return 0;
}

第一道

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void GetMemory(char* p)
{
	p = (char*)malloc(100);
}
//错误1:动态开辟了100个字节的空间,但是没有回收,因此会造成内存泄漏
void Test(void)
{
	char* str = NULL;
	GetMemory(str);//传值调用
	//将str的值传给了p,但GetMemory函数调用结束后,str的值仍然是NULL
	strcpy(str, "hello world");
	//错误2:对空指针进行解引用,程序会崩溃
	printf(str);
}
int main()
{
	Test();
	return 0;
}

将上述代码修改正确

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void GetMemory(char** p)
{
	*p = (char*)malloc(100);
}
void Test(void)
{
	char* str = NULL;
	GetMemory(&str);
	strcpy(str, "hello world");
	printf(str);
	//回收空间
	free(str);
	str = NULL;
}
int main()
{
	Test();
	return 0;
}

第二道(返回栈空间的地址的问题)

#include<stdio.h>
char* GetMemory(void)
{
	char p[] = "hello world";
	return p;
}
//将数组p首字符的地址返回给str后,数组就销毁了(局部变量出了作用域就会销毁),此时str就是野指针
void Test(void)
{
	char* str = NULL;
	str = GetMemory();
	printf(str);//随机值
}
int main()
{ 
	Test();
	return 0;
}

第三道

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void Test(void)
{
	char* str = (char*)malloc(100);
	strcpy(str, "hello");
	free(str);
	//当str指向的动态开辟的空间被操作系统回收后,str就变成了野指针
	if (str != NULL)
	{
		strcpy(str, "world");//对野指针解引用,造成了非法访问
		printf(str);
	}
}

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

五、柔性数组

①何为柔性数组?

结构体在创建时最后一个成员变量(前面至少还有一个成员变量)是大小未知的数组,该数组就被称为柔性数组。

struct S
{
	int a;
	int arr[0];//并不是指数组arr的大小是0个字节
	//arr就是柔性数组
};

有些编译器中,上述代码可能会报错,可以改为下面的代码

struct S
{
	int a;
	int arr[];//arr就是柔性数组
};

②sizeof在计算包含柔性数组的结构体类型大小时,会忽略柔性数组所占的大小。

#include<stdio.h>
struct S
{
	int a;
	int arr[];
};
//其实这个结构体类型的大小应该是大于4个字节的,但是sizeof在计算包含柔性数组的结构体类型大小时,会忽略柔性数组的大小
int main()
{
	printf("%zd\n", sizeof(struct S));//4
	return 0;
}

③包含柔性数组的结构体类型最好用malloc函数为其分配内存,并且分配的空间要足够大,以适应柔性数组的大小。

在这里插入图片描述

六、C/C++中程序中内存区域的划分

在这里插入图片描述

标签:10,NULL,str,int,动态内存,空间,include,分配
From: https://blog.csdn.net/2402_84440417/article/details/143404945

相关文章

  • Mit6.S081笔记Lab5: Lazy Page Allocation 惰性分配
    课程地址:https://pdos.csail.mit.edu/6.S081/2020/schedule.htmlLab地址:https://pdos.csail.mit.edu/6.S081/2020/labs/lazy.html我的代码地址:https://github.com/Amroning/MIT6.S081/tree/lazyxv6手册:https://pdos.csail.mit.edu/6.S081/2020/xv6/book-riscv-rev1.pdf相关翻......
  • 伙伴系统和slab分配器
    伙伴系统(buddysystem)当一个请求需要分配m个物理页,buddysystem会寻找一个有\(2^n\)页的块(\(2^n-1<m<2^n\))分配给他。我们使用一个空闲链表数组实现buddysystem,其中a[i]代表块大小为\(2^i个页\)(每页为4kb)假设我们要分配15kb内存,根据buddysystem,我们需要寻找一个16......
  • (C语言)动态内存管理,柔性数组
    1.为什么存在动态内存分配动态内存管理是C语言提供给我们自主维护空间大小的能力C语言提供了一个动态内存开辟的函数:void*malloc(size_tsize);这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。·如果开辟成功,则返回一个指向开辟好空间的指针。·......
  • 深入理解计算机系统 3.6 数组分配和访问
    C语言中的数组是一种将标量数据聚集成更大数据类型的方式。C语言实现数组的方式非常简单,因此很容易翻译成机器代码。C语言一个不同寻常的特点是可以产生指向数组中元素的指针,并对这些指针进行运算。在机器代码中,这些指针会被翻译成地址计算。3.6.1 基本原则对于数据类型T和......
  • c语言:动态内存管理中的malloc和free,calloc和realloc
    为什么要有动态内存分配?通过之前的学习,我们已经掌握的内存开辟方式有:inta=20;//在栈空间上开辟四个字节chararr[10]={0};//在栈空间上开辟10个字节的连续空间上述空间的开辟的大小是固定的数组在申明的时候,必须指定数组的长度,数组空间一旦确定了大小不能进行调整。......
  • 在K8S中,假设一家公司希望通过采用新技术来优化其工作负载的分配,公司该如何有效地实现
    在Kubernetes(K8s)中,一家公司若希望通过采用新技术来优化其工作负载的分配,可以遵循一系列策略和方法来实现高效的资源分配。以下是一些详细的建议:1.评估与规划资源需求评估:对公司现有的工作负载进行全面的资源需求评估,包括CPU、内存、存储和网络等资源。根据工作负载的特点,将......
  • 基于SpringBoot + Vue的在线项目管理与任务分配中的应用
    文章目录前言一、详细操作演示视频二、具体实现截图三、技术栈1.前端-Vue.js2.后端-SpringBoot3.数据库-MySQL4.系统架构-B/S四、系统测试1.系统测试概述2.系统功能测试3.系统测试结论五、项目代码参考六、数据库代码参考七、项目论文示例结语前言......
  • string和初学指针和动态内存分配
    strcmp:原型定义于:<string.h>intstrcmp(constchar*str1,constchar* str2)比较的标准是ASCII从第一个字符开始比,直到遇到不同的字符或者返回NULL(0)若STR1[I]>STR2[I],返回1若STR1[I]<STR2[I],返回-1若STR1[I]=STR2[I],返回0strcpy:原型定义于<string.h>常用于字符串......
  • 动态内存管理详解
    目录1.为什么要有动态内存分配2.malloc和free2.1malloc2.2free3.calloc和realloc3.1calloc3.2realloc4.常⻅的动态内存的错误4.1对NULL指针的解引⽤操作4.2对动态开辟空间的越界访问4.3对⾮动态开辟内存使⽤free释放4.4使⽤free释放⼀块动态开辟内存的......
  • C语言:动态内存管理
    目录为什么要有动态内存管理mallocfreecallocrealloc为什么要有动态内存管理内存分为栈区、堆区、静态区,每个区存放的变量如下图:目前我们掌握的内存开辟方法有:创建结构体structs{inti;intc;};创建一些变量:intx;intu[10];charl;还有创建联合等…......