学习笔记之内存处理

 

堆内存介绍:

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

堆内存管理:

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)",程序会异常停止。

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(p);

产生内存泄漏的原因:

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小时长度运行,即使程序只有很少的内存泄漏和内存碎片,长年累月下来也会导致可用的内存越来越来少,最后系统死机。

堆内存与栈内存的优缺点:

堆内存的优点:

1、空间大,理论上能使用将近3G的堆内存。

2、分配与释放时间可控,可长时间存储数据,也可以根据数据的使用情况随时释放内存。

3、根据实际情况动态调整内存块的大小,节约内存。

堆内存的缺点:

1、使用麻烦,需要手动调用标准库函数或系统接口进行分配、释放堆内存。

2、有可能产生内存碎片或内存泄漏。

3、内存块之间有空隙,内存利用率低。

栈内存的缺点:

1、空间小,数组定义过大,函数递归次数过多就会产生段错误。

2、分配和释放时间不受控制,它是根据函数的调用和结束自动分配、释放的,可能会出现,想长期用的会自动释放,已经用完的无法即时释放的情况。

3、使用栈内存的数组大小无法调用,为了保证数据能存储的下,基本上每次多定义,导致内存浪费。

栈内存的优点:

1、使用方便,由系统自动分配、释放。

2、按顺序分配、释放,不会产生内存碎片、内存泄漏。

3、变量、数组之间没有空隙,内存的使用率高。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
目录 第⼀部分 语⾔ 8 第 1 章 类型 9 1.1 变量 9 1.2 常量 10 1.3 基本类型 13 1.4 引⽤类型 14 1.5 类型转换 14 1.6 字符串 15 1.7 指针 17 1.8 ⾃定义类型 19 第 2 章 表达式 21 2.1 保留字 21 2.2 运算符 21 2.3 初始化 22 2.4 控制流 23 第 3 章 函数 29 3.1 函数定义 29 3.2 变参 30 3.3 返回值 30 3.4 匿名函数 32 3.5 延迟调⽤ 34 3.6 错误处理 35 第 4 章 数据 39 4.1 Array 39 4.2 Slice 40 4.3 Map 45 4 Go 学习笔记, 第 4 版 4.4 Struct 47 第 5 章 ⽅法 53 5.1 ⽅法定义 53 5.2 匿名字段 54 5.3 ⽅法集 56 5.4 表达式 56 第 6 章 接⼝ 60 6.1 接⼝定义 60 6.2 执⾏机制 62 6.3 接⼝转换 63 6.4 接⼝技巧 65 第 7 章 并发 66 7.1 Goroutine 66 7.2 Channel 68 第 8 章 包 76 8.1 ⼯作空间 76 8.2 源⽂件 76 8.3 包结构 77 8.4 ⽂档 81 第 9 章 进阶 82 9.1 内存布局 82 9.2 指针陷阱 83 9.3 cgo 86 9.4 Reflect 94 第⼆部分 源码 109 1. Memory Allocator 110 1.1 初始化 112 1.2 分配流程 117 5 Go 学习笔记, 第 4 版 1.3 释放流程 131 1.4 其他 135 2. Garbage Collector 140 2.1 初始化 140 2.2 垃圾回收 141 2.3 内存释放 155 2.4 状态输出 160 3. Goroutine Scheduler 166 3.1 初始化 166 3.2 创建任务 171 3.3 任务线程 178 3.4 任务执⾏ 184 3.5 连续栈 196 3.6 系统调⽤ 207 3.7 系统监控 211 3.8 状态输出 217 4. Channel 218 4.1 初始化 218 4.2 收发数据 220 4.3 选择模式 227 5. Defer 235 6. Finalizer 241 第三部分 附录 249 A. ⼯具 250 1. ⼯具集 250 2. 条件编译 251 3. 跨平台编译 253 4. 预处理 254 6 Go 学习笔记, 第 4 版 B. 调试 255 1. GDB 255 2. Data Race 255 C. 测试 258 1. Test 258 2. Benchmark 260 3. Example 261 4. Cover 261 5. PProf 262
GoLang学习笔记主要包括以下几个方面: 1. 语法规则:Go语言要求按照语法规则编写代码,例如变量声明、函数定义、控制结构等。如果程序中违反了语法规则,编译器会报错。 2. 注释:Go语言中的注释有两种形式,分别是行注释和块注释。行注释使用`//`开头,块注释使用`/*`开头,`*/`结尾。注释可以提高代码的可读性。 3. 规范代码的使用:包括正确的缩进和空白、注释风格、运算符两边加空格等。同时,Go语言的代码风格推荐使用行注释进行注释整个方法和语句。 4. 常用数据结构:如数组、切片、字符串、映射(map)等。可以使用for range遍历这些数据结构。 5. 循环结构:Go语言支持常见的循环结构,如for循环、while循环等。 6. 函数:Go语言中的函数使用`func`关键字定义,可以有参数和返回值。函数可以提高代码的重用性。 7. 指针:Go语言中的指针是一种特殊的变量,它存储的是另一个变量的内存地址。指针可以实现动态内存分配和引用类型。 8. 并发编程:Go语言提供了goroutine和channel两个并发编程的基本单位,可以方便地实现多线程和高并发程序。 9. 标准库:Go语言提供了丰富的标准库,涵盖了网络编程、文件操作、加密解密等多个领域,可以帮助开发者快速实现各种功能。 10. 错误处理:Go语言中的错误处理使用`defer`和`panic`两个关键字实现,可以有效地处理程序运行过程中出现的错误。 通过以上内容的学习,可以掌握Go语言的基本语法和编程思想,为进一步学习和应用Go语言打下坚实的基础。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Golang学习笔记](https://blog.csdn.net/weixin_52310067/article/details/129467041)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [golang学习笔记](https://blog.csdn.net/qq_44336275/article/details/111143767)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值