C-堆内存

堆内存

1、什么是堆内存

是进程中的一个内存段(heap),由程序员手动管理(手动申请和释放)
优点:足够大
缺点:使用麻烦且具备一定危险性

2、为什么要使用堆内存

1、随着程序的复杂,数据量增多,其他内存段已经不够用了
2、其它内存段的申请和释放不受控制,但是堆内存的申请和释放是受控制

3、如何使用堆内存malloc/free/calloc/realloc

注意:C语言中没有任何管理堆内存的语句,只能通过标准库的函数进行管理堆内存

  • malloc
void *malloc(size_t size);
    功能:从堆内存中申请size个字节的连续内存,内存中的数据值不确定
    返回值:成功时返回该内存的首地址,失败时返回NULL

int* p = malloc(4);
  • free
void free(void *ptr);
    功能:释放一块连续的堆内存

free(p);

注意:free仅仅是释放使用权,但是内存中的数据不会全部清理
注意:不能重复释放,必须释放有效内存
注意:可以free(NULL),但无意义

  • calloc(初始化为0)
void *calloc(size_t nmemb, size_t size);
    功能:从堆内存中申请nmemb块(个数),每块size(每个的长度)个字节的内存,得到的依然是一块连续的内存

注意:通过calloc申请到的内存会被初始化为0

  • realloc
void *realloc(void *ptr, size_t size);
    功能:改变已有堆内存块的大小,
    ptr:旧堆内存首地址
    size:调整后的堆内存大小
    返回值:成功返回调整后的堆内存首地址

注意:一定要重新接收,因为有可能不是在原地址上调整
如果无法在原基础上调整大小:
1、申请一块符合新要求的连续内存
2、拷贝旧内存中数据到新内存中
3、释放旧内存,并返回新内存首地址

4、malloc的内存管理机制

1、当首次通过malloc申请堆内存时,malloc会向操作系统申请内存,操作系统会直接分配33页(1页=4096字节)交给malloc管理,这样可以减少操作系统的运转次数,但是这样不意味着可以随意越界访问,因为malloc可能会继续分配给其他人,如果越界就会产生脏数据

int* p = malloc(4);

for(int i=0; i<1024*33-2; i++)    //最多只能访问到倒数第二个元素位置,最后一个元素地址被用掉了
{
        p[i] = 10;    
}

2、连续通过malloc申请内存时,每个内存块之间会有一些空隙(4~12字节),这些空隙一部分是为了访问内存对齐,可以加快内存的访问速度,其中有4个字节的空隙是为了记录malloc的维护信息,如果维护信息被破坏,会导致下一次free时出现内存崩溃

int* p1 = malloc(12);
int* p2 = malloc(12);
int* p3 = malloc(12);
printf("%p\n",p1);    //0x84e8008
printf("%p\n",p2);    //0x84e8018
printf("%p\n",p3);    //0x84e8028

5、堆内存越界的后果

1、一切正常
2、段错误
3、脏数据
4、如果破坏了malloc的维护信息,会影响下一次free

6、使用堆内存需要注意的问题

6.1 内存泄漏

6.1.1 原因:

程序向系统申请分配了一块内存(new/malloc)给对象使用,程序使用完这块内存后没有释放delete/free),导致这个不使用的对象一直占据内存单元,造成系统将不能再把它分配给需要的程序。即内存分配与内存释放没有做到一对一的匹配

一些常见的导致内存泄漏的代码问题:

1)new[]和delete不匹配,或者malloc和free不匹配。

2)指针作为返回值时忘记释放。

3)指针被多次复制,只有一次释放。

4)忘记释放在try块中的内存。

5)忘记释放指向堆内存的全局变量指针。

6)使用循环分配内存时,忘记在循环结束时释放内存。

6.1.2 危害:

堆内存被不断的分配使用,没有及时回收,随着程序的运行,堆内存会慢慢的被消耗殆尽,当其他程序需要内存时,系统无法及时的分配合适的内存供其使用,将会造成程序崩溃。

6.1.3 如何避免内存泄露

谁申请的谁释放,谁知道该释放谁释放

6.1.4 定位内存泄漏

  1. 使用工具:使用专门的内存检测和分析工具,如Valgrind、Dr. Memory等;代码分析工具 mtrace。这些工具可以帮助你跟踪程序中的内存分配和释放操作,检测到潜在的内存泄漏。

  2. 重载new/delete操作符:在C++中,你可以重载全局的new和delete操作符,在其中添加自定义的跟踪代码来记录内存分配和释放情况,并进行统计和分析。

  3. 记录内存分配:在程序中追踪每次内存分配的位置和大小信息。可以通过重载全局的malloc/free函数,在其中添加额外的记录代码来实现。或者使用自定义的内存管理类,重写其alloc/free函数,在其中记录相应信息。

  4. 统计未释放资源:在程序退出前或者特定时间点,遍历已分配但未释放的资源列表,并输出相关信息。这样就可以确定哪些资源没有正确释放。

  5. 内存监控日志:在程序中添加日志功能,记录每次内存分配和释放操作以及相关调用栈信息。通过分析日志文件,可以找出潜在的泄漏点。

6.查看进程的内存使用情况
windows:任务管理器
Linux: ps -aux

6.2 内存碎片

已经释放了但是又无法使用的内存叫做内存碎片,是由于申请和释放的时间不协调导致的,只能尽量减少不能避免

如何减少内存碎片的产生:
1、尽量申请大块内存自己管理
2、不要频繁地申请释放内存
3、使用栈内存不会产生内存碎片

7、内存清理函数bzero/memset

  • bzero: 置0
#include <strings.h>
void bzero(void *s, size_t n);
    功能:把一块内存全部清理为0
    s:内存块的首地址
    n:要清理的内存字节数
  • memset:置为c
#include <string.h>
void *memset(void *s, int c, size_t n);
    功能:把内存块按字节设置为c
    s:内存块的首地址
    c:想要设置的ASCII码值
    n:要设置的内存字节数
    返回值:返回设置后的内存首地址 s        

链式调用:一个函数的返回值可以作为另一个函数的参数

free(memset(p,0,100));

8、堆内存中定义二维数组

8.1  指针数组

类型名* arr[n];
        for(int i=0; i<n; i++)
        {
            arr[i] = malloc(sizeof(类型)*m);
         }
申请到 n行m列 的二维数组,每行内存可能不连续

注意:每一行的m值可不同,可以得到不规则的二维数组

缺点:容易产生内存碎片

优点:可以不规则、容易申请成功

8.2 数组指针

类型名 (*arrp)[m] = malloc(sizeof(类型)*m*n);
申请到 n行m列 的二维数组,并且全部内存都是连续的

优点:不容易产生内存碎片

缺点:相对而已对内存要求更高

注意:无论哪种方式申请,最后都是当做二维数组访问arr[i][j]

posted @   冲他丫的  阅读(37)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 上周热点回顾(3.3-3.9)
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
点击右上角即可分享
微信分享提示