前言
现代计算机基本都是基于冯诺伊曼结构体系设计出来的,冯诺伊曼结构体系的核心就是“存储程序”,将程序(指令集)和数据以同等地位存储在内存中。但是我们的内存空间并不是无限大的,所以为了高效的利用好内存空间,操作系统会对这些内存空间进行相应的分区,不同区域的内存有其对应的功能和使用方式。
比如局部变量、函数形参通常是存储在栈区的,这部分内存空间的特点就是临时使用,用完即释放(当然这个都是由操作系统自动完成的,不需要程序员的干预);
再比如全局变量通常存放在静态区,此外由static修饰的局部变量也会放到静态区(所以static修饰局部变量,本质上是改变了其存储的位置,从栈区-- > 静态区),这部分内存空间就是生命周期很长,长到整个程序运行结束;
再例如我们使用的常量字符串,会被保存到常量区,这部分内存区域的特点就是类似于“常量”,不可被修改,相当于添加了一个“const”的buff。
(我的废话:在学习的时候如果可以多多思考,联系不同的知识点,当将这些内容串起来时,可以形成一个比较宏观、整体的角度。更进一步,如果我们能够从中找到乐趣就更好了!现在我们进入正题吧!)
全文结构:
文章目录
一、寻根问底
什么是动态内存分配 / 管理?
由程序员根据实际编程需要向操作系统申请,在堆区上开辟的,供程序员操作使用和维护的内存空间,程序员的游乐园!通常是一些临时用到的数据或者变量,随时开辟,用完随时释放,而不必等到函数结束后由操作系统回收!
为什么需要动态内存分配?
实际编程中,不仅需要大小固定的内存空间,往往还需要大小可变的内存空间。
比如说如果我们要建立一个通讯录,用来存放相关信息(姓名、性别、年龄、电话等),这种复杂的数据,我们知道要用结构体类型来存储,而且要用结构体数组来存,但是问题是“这个数组的大小应该多大呢?”
这个问题确实不好回答,如果长度给小了,那么就会导致数据溢出,进而引发程序崩溃。
如果给大了,又会导致存储空间大量浪费,空间利用率低。
为了解决这一类问题,就出现了动态内存分配,内存空间按需索取,要多少给多少!
怎么建立动态内存分配?
通过系统提供的4个库函数实现,malloc\calloc\realloc\free,这四个函数后面我们会详细介绍。
二、动态内存函数
注意:以下说的四个函数的头文件均为:stdlib.h
malloc
函数原型:void * malloc(size_t size);
size_t就是unsigned int(无符号整型)
这个函数的作用就是在动态存储区中分配一个长度为size个字节的连续空间,并返回指向该空间的指针。
1)如果开辟成功,则返回一个指向开辟好空间的指针。
2)如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
3)返回值的类型是void * ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。
4)如果参数size为0,malloc的行为是标准是未定义的,取决于编译器。
动态开辟的空间如何释放和回收呢?
C语言提供了一个专门完成这个功能的库函数-- - free
free
函数原型:void free(void* p)
free的作用就是释放指针变量p所指向的动态空间,使这部分空间能够重新被利用。
1)如果参数ptr指向的空间不是动态开辟的,那free函数的行为是未定义的。
2)如果参数 ptr是NULL指针,则函数什么事都不做。
现在来看一下实际的使用:
#include<stdio.h>
#include<stdlib.h>
int main()
{
//1.通过动态开辟申请10个int类型的空间
int* ptr = (int*)malloc(10 * sizeof(int));//通常结合sizeof一起使用
//根据实际使用强制类型转换为想要的类型
//2.malloc有可能申请空间失败,所以需要判断一下
if (ptr == NULL)
{
perror("main");//perror是一个报错函数,实际出错时打印效果为:main:xxxxxx(错误原因)
return 0;//出错就直接结束函数
}
//3.使用 给这10个整型空间赋值
for (int i = 0; i < 10; i++)
{
*(ptr + i) = i;
}
//打印一下
for (int i = 0; i < 10; i++)
{
printf("%d ", ptr[