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;
相关推荐
动词ing1 小时前
【学习笔记】C语言(数组指针与指针数组+字符数组+函数+参数传递+字符串作为形参+递归函数+指针函数+回调函数+结构体嵌套+内存动态分配函数)
c语言·笔记·学习
汉字萌萌哒2 小时前
2024CSP-J入门级C++真题详解
开发语言·c++
arbboter2 小时前
【网络工具】NetProxy网络代理用户手册
开发语言·网络·c#·网络代理·网络异常·代理工具
汉字萌萌哒3 小时前
2019CCF-CSP入门级C++试题解析
java·开发语言·c++
叠层归一研究院3 小时前
AGI 系统(十一):二阶网络 — 二阶递归 × 多主体 (元符号网络)
开发语言·人工智能·算法·php·agi
小雨笙笙3 小时前
C语言:变量三属性——存储类型
c语言·开发语言
数据知道4 小时前
反序列化漏洞:Java、PHP、Python 三条线各讲透
java·网络·python·安全·网络安全·php
果果燕4 小时前
C++ 学习笔记(二):类与对象—— 构造函数与析构函数
开发语言·c++
江畔柳前堤4 小时前
Function Calling 与 Tool Calling:从认知到工程的全景深度解析
开发语言·网络·人工智能·深度学习·算法·机器学习·php