C语言学习第十一天

堆内存介绍:
一段由程序员手动管理的内存段,特点:空间大,需要手动申请、释放,没有固定的使用顺序哪块合适使用哪块。

堆内存管理:
C语言中没有管理堆内存的语句,C标准库中提供一套管理堆内存的函数,这些函数底层封装了各种系统管理堆内存的接口,所以可以跨平台使用。
#include <stdlib.h>

void *malloc(size_t size);
功能:向malloc申请位于堆内存段的size字节的内存块
size:要申请的内存块的大小,如果使用malloc申请数组形式的内存块,size=sizeof(数组元素类型)*数组长度
返回值:成功返回内存块首地址,失败返回NULL(现在有堆内存无法满足size个字节的要求)
    如果size等于0,返回NULL或唯一个地址,并且该地址可以通过free释放。
注意:使用malloc申请到的内存块,里面的内容是不确定的,malloc不会帮我们初始化。
    void bzero(void *s, size_t n);
    功能:把s内存块的n个字节清理为0
    void *memset(void *s, int c, size_t n);
    功能:把s内存块的n个字节设置为c,c的范围是:0~255,以字节为单位。

void free(void *ptr);
功能:释放堆内存(释放的是使用权,只破坏内存块的一部分内存,大部分数据还在)。 
ptr:要释放的内存块的首地址,它必须是malloc、calloc、realloc函数的返回值,如果ptr==NULL则free不会执行任何操作。
注意:如果一个内存被重复释放,会出现"double free or corruption (fasttop)",程序会异常停止。

练习1:对一百万个0~255之间的整数进行排序。

void *calloc(size_t nmemb, size_t size);
功能:申请nmemb个size个字节的内存块,专门用于申请数组型的内存块。
nmemb:数组的长度
size:数组元素的字节数
返回值:成功返回内存块首地址,失败返回NULL(现在有堆内存无法满足size个字节的要求)
    如果size或nmemb等于0,返回NULL或唯一个地址,并且该地址可以通过free释放。
注意:使用calloc申请的内存块,所有字节会被初始化0。

练习2:自定义实现一个calloc函数。


void *realloc(void *ptr, size_t size);、
功能1:把已有的内存块调小,ptr是malloc、calloc、realloc的返回值,size必须小于之前的字节数。
功能2:把已有的内存块调大,ptr是malloc、calloc、realloc的返回值,如果ptr后续的内存没有被占用,realloc会在ptr的基础上进行扩大,如果ptr后续的内存已经被占用,realloc会重新分配一块符合要求的内存块,并把ptr上的内存拷贝到新的内存块,然后释放ptr,再返回新内存块的首地址,必须重新接收realloc函数的返回值。
功能3:释放内存,ptr是malloc、calloc、realloc的返回值,0==size,此时realloc的功能就相当于free
功能4:申请内存,NULL==ptr,0<size,此时的功能就相当于malloc。

练习3:计算出100~10000所有的素数存储在数组中备用,要求不能浪费内存。

malloc的堆内存管理机制:
1、当程序首次向malloc申请内存时,此时malloc手里没有堆内存可分配,malloc会向操作系统申请内存,操作系统会一次性分配33页内存交给malloc管理(一页内存=4096个字节),之后再向malloc申请内存时,malloc会从这33页内存中分配给用户。
2、当我们访问堆内存时,只要不超过33页范围,操作系统就不会判断为段错误。
3、使用malloc分配的每个内存块前面(4~12字节的空隙)
空隙前0~8个的空闲字节,用于内存对齐,这块内存可以使用。
空隙后4个字节,记录着malloc的管理信息(这也是为什么free只需要提供内存块的首地址),如果管理信息被破坏会影响malloc、free、calloc、realloc、printf、scanf函数的后续使用。

为什么访问以135160为下标的字节时出现段错误?
1、操作系统分配33页内存,也就是135168个字节给malloc。
2、malloc会拿出8个字节作为内存块前面的空隙,因此malloc返回的是33页内存的第9个字节的地址。
3、从malloc返回的地址开始还剩135160个字节,下标范围是:0~135159,所以当访问135160为下标的内存时就会被操作系统判定为段错误。

使用堆内存越界时会产生什么后果:
int* p = malloc(4);
1、如果越界使用的是空隙中的空闲字节,一切正常。
p[0] = 123; // 合法的正常访问
p[1] = 123; // 空隙中的空闲字节
p[2] = 123; // 空隙中的空闲字节

2、如果越界使用的是空隙中的malloc管理信息,将会影响malloc、free、calloc、realloc、printf、scanf函数的后续使用。
p[3] = 0; // 空隙中的malloc管理信息

malloc.c:2401: sysmalloc: Assertion `(old_top == initial_top (av) && old_size == 0) || ((unsigned long) (old_size) >= MINSIZE && prev_inuse (old_top) && ((unsigned long) old_end & (pagesize - 1)) == 0)' failed.

*** Error in `./xxxx': free(): invalid pointer: 0x08edxxxx ***

3、如果使用的是malloc还没有分配出去的内存,此时不会出现段错误、也不会影响malloc、free的后续使用,但malloc这块内存分配出去后,越界存储的数据就会被覆盖的风险,这种情况叫脏数据。
p[4] = 6666;
int* p1 = malloc(4);
p1[0] = 1234;
printf("%d\n",p[4]); // 输出的结果是1234

4、如果超出33页范围,会被操作系统判定为段错误。
p[33790] = 1234; // 超出了33页范围,会产生段错误

内存碎片:
什么是内存碎片:已经释放了使用权,但无法被malloc再次分配出的内存块叫内存碎片。
int* p1 = malloc(4);
int* p2 = malloc(4);
int* p3 = malloc(4);
free(p2);
int* p4 = malloc(16); // 此时p2内存块就是内存碎片

内存碎片产生的原因:释放和分配的时间、大小不协调导致的。
内存碎片无法杜绝只能尽量减少,减少内存碎片的方法:
    1、尽量使用栈内(Linux系统查看栈内存的使用上限:ulimit -s,设置栈内存的使用上限:ulimit -s <size>,当前系统栈内存的使用上限:8192kb->8mb)。

    2、尽量分配大块内存,让用户自己管理。
    int* p1 = malloc(16);
    int* p2 = p1+1;
    int* p3 = p2+1;
    int* p4 = p3+1;

    3、按照分配的顺序,逆序释放,也就是把堆内存当栈管理。
    int* p1 = mallor(4);
    int* p2 = mallor(4);
    int* p3 = mallor(4);
    free(p3);
    free(p2);
    free(p1);

    4、内存碎片整理
    int* p1 = mallor(4);
    int* p2 = mallor(4);
    int* p3 = mallor(4);

    memcpy(p2,p3,4);
    swap(p2,p3);
    free(p2);

内存泄漏:
什么是内存泄漏:已经不再使用,但无法释放的内存叫内存泄漏。
int* p = malloc(4); // 此次分配的内存无法正常释放
*p = 1234;
printf(“%d\n”,*p);
p = malloc(8);

free§;

产生内存泄漏的原因:
    1、只写的内存分配语句,而没有内存写释放语句,可以粗心大意,也可以是以为其它人会写。
    2、写了内存释放语句,但由于执行流程、条件设计有问题,导致释放语句无法执行。
    3、与堆内存配合的指针变量被破坏,导致free执行无效。

如何减少内存泄漏:
    1、按照规则分配、释放内存
        自用:谁申请谁释放,分配和释放语句成对出现。
        共用:谁知道该释放谁释放,项目组中负责分配和负责释放人进行对接。
    2、封装malloc、free函数,记录每一块分配、释放的内存块。
    3、使用cosnt保存与堆内存配合的指针变量不被破坏。
        int* const p = malloc(4);
        p = malloc(8); // 出错
        free(p);
    
如何检测是否发生内存泄漏:
    1、查看系统的内存使用情况,是否出现暴涨的情况,可通过ps命令查看或maps文件查看。
    2、随着程序的长时间运行,可用的内存越来越少,则大概率出现内存泄漏。
    3、使用gdb调试工具,查看内存的使用情况。
    4、使用valgrind检查可执行程序
        sudo apt install valgrind
        valgrind --tool=memcheck --leak-check=yes <可执行程序>
    5、分析malloc、free的执行日志。

内存泄漏、内存碎片的危害:
    前提:当程序结束时,操作系统分配它的所胡资源都会被回收,当应用端程序出内存泄漏、内存碎片、程序卡顿时,重启程序即可。

    服务端的程序一般需要7*24小时长度运行,即使程序只有很少的内存泄漏和内存碎片,长年累月下来也会导致可用的内存越来越来少,最后系统死机。

堆内存和栈内存的优缺点?
管理规则:
栈内存:由系统自动管理,先进后出,所以叫栈内存。
堆内存:由程序员调用标准库接口手动,没使用固定的分配顺序,数据没有规律的堆在该内存段,所以叫堆内存。
大小:
栈内存:大小有限,查看方法:ulimit -s 设置方法:ulimit -s 当前系统栈内存的使用上限:8192kb=8mb
堆内存:空间足够大,理论上接近3G
安全性:
栈内存:不会出现内存泄漏,内存碎片
堆内存:容易出现内存泄漏,内存碎片
存储的数据类型:
栈内存:量少,临时性的
堆内存:量大,需要长期持有
栈内存优点:安全、方便、内存利用率高
栈内存缺点:空间小,释放时间不可控
堆内存优点:空间大,分配与释放受控制,可长期存储大量数据。
堆内存缺点:使用麻烦,安全性低,容易出现内存泄漏、内存碎片,内存利用率低,内存块之间有4~12字节的空隙。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值