C语言中的动态内存分配

在很多场景,数据的大小是在运行时才能确定,编译阶段无法确定数据长度;或者有大块的数据需要存放,但是栈空间不够;或者某些参数需要实现跨函数传递;或者需要实现可变的数据结构。当面临这些问题时,我们往往想到动态分配内存。本文将主要讲在C语言中,动态内存分配的4个常用函数,以及使用注意事项。

一、为什么要用动态内存

栈内存存放局部变量,大小编译期确认,空间很小(通常只有几MB),函数结束自动销毁

堆内存空间大,动态分配,大小运行时确定,但是必须手动管理

原因1:【主要原因】运行时才知道需要多大内存

静态数组写大了浪费内存,写小了会溢出,动态内存要多少给多少

例如:用户输入、读取文件、网络接收的数据,程序跑起来才知道要多少数据,没有办法提前写死。

原因2:突破栈空间限制

栈空间很小,一般就1-8MB左右,大数据不能放在栈上,必须放到堆。

原因3:内存生命周期不受函数作用域限制

栈上的局部变量,函数执行完毕就自动释放了,不能继续函数外使用

动态内存:函数结束,堆内存依然存在,必须手动free才释放

eg:局部数组,函数返回后内存销毁,返回指针就是也指针

原因4:实现可边长数据结构

链表、队列、树、哈希表,全部离不开动态内存。

链表每新增一个节点,就要malloc一块内存节点(没有动态内存,就写不出链表)

二、四种常用动态内存函数

1、四种对比

函数 参数 内存初始化 功能
malloc malloc(字节数) 不初始化(垃圾值) 分配指定大小堆内存
calloc calloc(元素个数,每个大小) 全部置0 分配数组,自动清零
realloc realloc(旧指针,新总字节) 旧数据保留,新增部分不初始化 修改已分配内存大小,扩容缩容
free free(指针) - 释放堆内存

malloc和realloc分配的空间生存期,是随进程持续性,不是随代码块持续性。

2、malloc

作用:分配X字节的堆内存,不会初始化内存,里面为随机值。分配成功返回内存首地址,分配失败返回NULL

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

int main(){
    SetConsoleOutputCP(65001);
    SetConsoleCP(65001);
    //在堆上申请10个连续int内存空间
    int *p=(int*)malloc(10*sizeof(int));
    if(p==NULL){
        printf("动态内存分配失败\n");
    }
    else{
        printf("动态内存分配成功\n");
        for(int i=0;i<10;i++){
            printf("%p,存放的内容%lx\n",&p[i],p[i]);
        }
    }
    free(p);//用完释放
    p=NULL;// 置空,防止野指针
    return 0;
} 

运行结果:

malloc分配的内存不会自动初始化,所有元素默认是随机垃圾值,使用前建议手动赋值。

3、calloc

calloc(num,size)

作用:分配num个元素,每个占用size字节,分配完成自动全部初始化为0

和malloc的区别:calloc会清零,malloc不会。

c 复制代码
//上面的malloc变为calloc
int *p=(int*)calloc(10,sizeof(int));

运行结果:

4、realloc

realloc(ptr,new_size)

作用:重新修改已分配好的堆内存,扩容或缩容

ptr:之前得到的指针

newsize:新的总字节数

注意:

如果后面堆空间足够,直接原地扩容,返回原来的地址

如果后面不够,堆找一块全新内存,把旧内容复制过去,释放旧内存,返回新地址

失败返回NULL,原来旧内容不会释放

realloc(ptr,0):长度为0,是把原来的空间释放

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

int main(){
    SetConsoleOutputCP(65001);
    SetConsoleCP(65001);
    //在堆上申请10个连续int内存空间,并将内存的首地址赋值给整型指针p
    int *p=(int*)calloc(5,sizeof(int));
    int *tmp=(int*)realloc(p,10*sizeof(int));
    if(p==NULL)
        {
        printf("动态内存分配失败");
        }
    else{
        printf("-----动态内存分配成功-----\n");
         for(int i=0;i<5;i++){
            printf("%p,存放的内容%lx\n",&p[i],p[i]);
         }
        if(tmp!=NULL)
            {
            printf("-----重新分配成功-----\n");
            p=tmp;
            for(int i=0;i<10;i++)
                {
                printf("%p,存放的内容%lx\n",&p[i],p[i]);
                }
            }
        else
            {
            printf("-----重新分配失败-----\n");
            //分配失败,p仍指向旧内存,可以继续使用
            printf("%p\n",&p);
            }       
        }
    free(p);//用完释放
    p=NULL;// 置空,防止野指针
    return 0;
} 

运行结果:

注意:realloc直接用原指针接收返回值,失败会导致内存泄漏(例子中直接写p=realloc(p,newsize);如果失败p被赋值为NULL,原内存地址丢失,直接内存泄漏)。

realloc(NULL,n)等价于malloc(n)

5、free释放内存

作用:释放堆内存,还给操作系统

规则

参数必须是malloc、realloc、calloc返回的地址,不能传栈变量地址

free之后,原来的指针变量仍然保存旧地址,变为野指针,建议手动赋值为NULL

不能重复free同一个指针,程序会崩溃

free(NULL)是安全的,什么都不做

三、使用注意事项

1、实际项目大量使用动态内存(malloc、realloc、calloc、free),但是不能无脑使用,滥用会付出巨大代价

内存泄漏 :忘记free,程序越跑内存占用越高,长期运行服务直接卡死。

野指针 :free之后,继续使用,重复free,直接崩溃

realloc坑点 ,分配失败处理麻烦

频繁malloc、free会产生内存碎片,长时间运行内存效率下降

所以:

小固定大小缓冲区,优先栈数组,不要malloc

malloc和free尽量配对,谁申请,谁释放

嵌入式设备内存很小,尽量少用动态内存,必要时禁止malloc

2、垃圾内存/内存泄漏

c 复制代码
char *p=malloc(10);
char a;
p=&a;//p本来指向10字节的动态内存分配的空间,现在指向a

上述代码,没有任何指针会指向那段动态内存分配的空间,并且那段空间不会释放,变为垃圾内存(或说内存泄漏)

解决办法:

方案一:p指向其他对象之前先释放

c 复制代码
free(p);
p=&a;

方案二:p指向其他对象之前,给那段空间找一个索引指针

c 复制代码
char *q=p;//后面可以通过q来释放这段空间。
p=&a;
相关推荐
wuyk55539 分钟前
12.归并排序:分治思想的稳定排序算法
开发语言·数据结构·算法·排序算法
吴声子夜歌43 分钟前
ApacheCommons——commons-text(模板替换与文本算法)
java·开发语言·算法·apache
quantdash_cc1 小时前
量化数据源怎么选:稳定性、实时性和数据完整性到底该如何权衡?
开发语言·python·数据分析·量化交易·股票数据·quantdash
萧瑟余晖2 小时前
Java深入解析篇五十四之对象模型详解
java·开发语言
张文是假的啊2 小时前
Java | record | Controller逻辑
java·开发语言
不甘先生2 小时前
Go 中 type、方法与指针接收者:从 str_name.Name() 看懂 Go 的类型系统
开发语言·后端·golang
PHP实战开发录3 小时前
PHP权限系统设计为什么越改越复杂
系统架构·php·开发
quantdash_cc3 小时前
Python 股票 K 线数据质量校验:字段、缺失值、重复行和价格异常
开发语言·python·数据分析·量化交易·股票数据·quantdash
Zenova EdgeOS3 小时前
工业网关重试机制:从固定间隔到指数退避的工程实战
开发语言·网络·php
嘻哈baby3 小时前
Go 函数中的参数为什么不支持默认值?
java·开发语言·jvm