引言

  突然感觉要出去走走了, 醒了后 刷完牙就在联系coding, 不知不觉到了 黄昏.

看看天, 打开灯. 又感觉到了 夜夜夜夜 .

13年到北京务工, 遇到一批批NB的同龄人物. 一块工作, 一块喜欢锻炼, 一块默默的学习.

从他(她)们身上发现一个事实.

假如我们一样聪明,

  当你抱怨自己为什么努力了, 确还是 这么水的时候  ;   其实他(她)们在拼命. 而你只是在努力 ,

假如我们不一样聪明,

  如果还不能开挂,  那会是怎么样精彩 x x x x.

前言  -  内存越界处理

我们先看设计图. 内存越界检查原理如下

上面原理是不是很简单. 而这恰恰是最通用的做法. 美的东西不负责.  美很重要.

那我们按照上面设计思路. 首先构建 接口文件 checkmem.h

  1. #ifndef _H_MEMCHECK_CHECKMEM
  2. #define _H_MEMCHECK_CHECKMEM
  3.  
  4. #include <stddef.h>
  5.  
  6. /*
  7. * 对malloc进行的封装, 添加了边界检测内存块
  8. * (inline 原本分_DEBUG有宏处理, 后面没加等于没用)
  9. * sz : 申请内存长度
  10. * : 返回得到的内存首地址
  11. */
  12. extern inline void* mc_malloc(size_t sz);
  13.  
  14. /*
  15. * 对calloc进行封装, 添加边界检测内存块
  16. * cut : 申请的个数
  17. * sz : 每个的大小
  18. */
  19. extern inline void* mc_calloc(size_t cut, size_t sz);
  20.  
  21. /*
  22. * 对relloc进行了封装, 同样添加了边间检测内存块
  23. */
  24. extern inline void* mc_realloc(void* ptr, size_t sz);
  25.  
  26. /*
  27. * 对内存检测, 看是否出错, 出错直接打印错误信息
  28. * 只能检测, check_* 得到的内存
  29. */
  30. extern inline void mc_check(void* ptr);
  31.  
  32. #endif // !_H_MEMCHECK_CHECKMEM

主要是对 malloc, calloc, realloc 进行添加尾部和头部的内存块处理. 就这么简单一步. 假如能看懂上面设计思路图.

这些代码都可以跳过了.   思路比代码重要.  好那我们继续展现实现部分. checkmem.c

  1. #include "checkmem.h"
  2. #include <stdio.h>
  3. #include <errno.h>
  4. #include <string.h>
  5. #include <stdlib.h>
  6.  
  7. // 控制台打印错误信息, fmt必须是双引号括起来的宏
  8. #define CERR(fmt, ...) \
  9. fprintf(stderr,"[%s:%s:%d][error %d:%s]" fmt "\r\n",\
  10. __FILE__, __func__, __LINE__, errno, strerror(errno), ##__VA_ARGS__)
  11. //控制台打印错误信息并退出, t同样fmt必须是 ""括起来的字符串常量
  12. #define CERR_EXIT(fmt,...) \
  13. CERR(fmt,##__VA_ARGS__),exit(EXIT_FAILURE)
  14.  
  15. // 插入字节块的个数
  16. #define _INT_CHECK (1<<4)
  17.  
  18. /*
  19. * 对malloc进行的封装, 添加了边界检测内存块
  20. * sz : 申请内存长度
  21. * : 返回得到的内存首地址
  22. */
  23. inline void*
  24. mc_malloc(size_t sz) {
  25. // 头和尾都加内存检测块, 默认0x00
  26. char* ptr = calloc(, sz + * _INT_CHECK);
  27. if (NULL == ptr) {
  28. CERR_EXIT("malloc sz + sizeof struct check is error!");
  29. }
  30.  
  31. //前四个字节保存 最后一个内存块地址 大小
  32. size_t* iptr = (size_t*)ptr;
  33. *iptr = sz + _INT_CHECK;
  34.  
  35. return ptr + _INT_CHECK;
  36. }
  37.  
  38. /*
  39. * 对calloc进行封装, 添加边界检测内存块
  40. * cut : 申请的个数
  41. * sz : 每个的大小
  42. */
  43. inline void*
  44. mc_calloc(size_t cut, size_t sz) {
  45. return mc_malloc(cut*sz);
  46. }
  47.  
  48. /*
  49. * 对relloc进行了封装, 同样添加了边间检测内存块
  50. */
  51. inline void*
  52. mc_realloc(void* ptr, size_t sz) {
  53. // 先检测一下内存
  54. mc_check(ptr);
  55.  
  56. // 重新申请内存
  57. char* cptr = (char*)ptr - _INT_CHECK;
  58. char* nptr = calloc(, sz + * _INT_CHECK);
  59. if (NULL == nptr) {
  60. CERR_EXIT("realloc is error:%p.", ptr);
  61. }
  62. // 内存移动
  63. size_t* bsz = (size_t*)cptr;
  64. memcpy(nptr, cptr, *bsz < sz ? *bsz : sz);
  65. *bsz = sz;
  66.  
  67. free(cptr);
  68. return nptr;
  69. }
  70.  
  71. // 检测内存是否错误, 错误返回 true, 在控制台打印信息
  72. static void _iserror(char* s, char* e) {
  73. while (s < e) {
  74. if (*s) {
  75. CERR_EXIT("Need to debug test!!! ptr is : (%p, %p).check is %d!",s, e, *s);
  76. }
  77. ++s;
  78. }
  79. }
  80.  
  81. /*
  82. * 对内存检测, 看是否出错, 出错直接打印错误信息
  83. * 只能检测, check_* 得到的内存
  84. */
  85. inline void
  86. mc_check(void* ptr) {
  87. char *sptr = (char*)ptr - _INT_CHECK;
  88.  
  89. //先检测头部
  90. char* s = sptr + sizeof(size_t);
  91. char* e = sptr + _INT_CHECK;
  92. _iserror(s, e);
  93.  
  94. //后检测尾部
  95. size_t sz = *(size_t*)sptr;
  96. s = sptr + sz;
  97. e = s + _INT_CHECK;
  98. _iserror(s, e);
  99. }

代码实现都很中规中矩, 比较容易. 也就百行. 按照接口文件一个个看实现. 很容易学到开发中技巧. 提高实战技巧.

扯一点, C, C++ 老开发人员水平都比较高, 不喜欢写注释. 这个强烈推荐不是大牛的选手一定要多写注释.

不要扯 什么  <代码即注释> . 多写注释容易加深自己二次思考, 加快自己的成长. 不要和老开发人学这个 , 如果你跳槽, 遇到一个大项目

注释等价无, 你是什么感受. 为我们多留条后路, 多写注释.

好 看测试代码 main.c

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include "checkmem.h"
  4.  
  5. /*
  6. * 演示一种检测内存越界的办法
  7. * 添加上下限方式
  8. */
  9. int main(int argc, char* argv[]) {
  10.  
  11. // 实验步骤是, 是申请内存, 在操作内存
  12. char* as = mc_malloc();
  13.  
  14. mc_check(as);
  15.  
  16. // 内存越界了
  17. //as[16] = 18;
  18. //mc_check(as);
  19.  
  20. // 重新分配内存, 再次越界
  21. as = mc_realloc(as, );
  22. as[] = ;
  23. mc_check(as);
  24.  
  25. free(as);
  26. return ;
  27. }

测试结果

到这里内存越界的思路和实现都已经完毕了.欢迎思考尝试.

正文 - 内存全局监测

  内存全局检测思路更简单. 采用引用'计数方式'处理. 扯一点很多自动垃圾回收机制都采用了引用计数方式.

包括内核层例如 文件描述符, IPC 共享内存, 消息机制等.  先看接口 memglobal.h

  1. #ifndef _H_MEMGLOBAL_MEMGLOBAL
  2. #define _H_MEMGLOBAL_MEMGLOBAL
  3.  
  4. #include <stddef.h>
  5. #include <stdlib.h>
  6.  
  7. /*
  8. * 全局启动内存简单监测
  9. */
  10. extern inline void mg_start(void);
  11.  
  12. /*
  13. * 增加的全局计数的 malloc
  14. * sz : 待分配内存大小
  15. * : 返回分配的内存首地址
  16. */
  17. extern void* mg_malloc(size_t sz);
  18.  
  19. /*
  20. * 增加了全局计数的 calloc
  21. * sc : 分配的个数
  22. * sz : 每个分配大小
  23. * : 返回分配内存的首地址
  24. */
  25. extern inline void* mg_calloc(size_t sc, size_t sz);
  26.  
  27. /*
  28. * 增加了计数的 realloc
  29. * ptr : 上一次分配的内存地址
  30. * sz : 待重新分配的内存大小
  31. * : 返回重新分配好的内存地址
  32. */
  33. extern void* mg_realloc(void* ptr, size_t sz);
  34.  
  35. /*
  36. * 增加了计数处理的内存 free
  37. * ptr : 上面函数返回地址的指针
  38. */
  39. extern inline void mg_free(void* ptr);
  40.  
  41. // 在测试模式下开启 全局内存使用计数
  42. #if defined(_DEBUG)
  43. # define malloc mg_malloc
  44. # define calloc mg_calloc
  45. # define realloc mg_realloc
  46. # define free mg_free
  47. #else
  48. # define malloc malloc
  49. # define calloc calloc
  50. # define realloc realloc
  51. # define free free
  52. #endif
  53.  
  54. #endif // !_H_MEMGLOBAL_MEMGLOBAL

还是比较优美的. 再看 memglobal.c

  1. #include "memglobal.h"
  2. #include <stdio.h>
  3. #include <errno.h>
  4. #include <string.h>
  5. #include <stdlib.h>
  6.  
  7. // 取消内置宏, 防止递归
  8. #undef malloc
  9. #undef calloc
  10. #undef realloc
  11. #undef free
  12.  
  13. // 控制台打印错误信息, fmt必须是双引号括起来的宏
  14. #define IOERR(io, fmt, ...) \
  15. fprintf(io,"[%s:%s:%d][error %d:%s]" fmt "\r\n",\
  16. __FILE__, __func__, __LINE__, errno, strerror(errno), ##__VA_ARGS__)
  17.  
  18. // 全局内存计数, 系统第一次构造的时候为0
  19. static int _mct;
  20.  
  21. #define _STR_MGTXT "checkmem.log"
  22.  
  23. // mg内存监测退出时候, 记录一些信息
  24. static void _mg_exit(void) {
  25. if (_mct == ) return;
  26.  
  27. // 先打印信息到控制台
  28. IOERR(stderr, "Detect memory leaks _mct = %d!!", _mct);
  29.  
  30. //输出到文件
  31. FILE* txt = fopen(_STR_MGTXT, "a");
  32. if (txt == NULL) {
  33. IOERR(stderr, "fopen " _STR_MGTXT " a is error!");
  34. return;
  35. }
  36. IOERR(txt, "Detect memory leaks _mct = %d!!", _mct);
  37. fclose(txt);
  38. }
  39.  
  40. /*
  41. * 全局启动内存简单监测
  42. */
  43. inline void
  44. mg_start(void) {
  45. // 注册退出监测事件
  46. atexit(_mg_exit);
  47. }
  48.  
  49. /*
  50. * 增加的全局计数的 malloc
  51. * sz : 待分配内存大小
  52. * : 返回分配的内存首地址
  53. */
  54. void*
  55. mg_malloc(size_t sz) {
  56. void* ptr = malloc(sz);
  57. if (!ptr) return NULL;
  58. ++_mct;
  59. memset(ptr, 0x00, sz);
  60. return ptr;
  61. }
  62.  
  63. /*
  64. * 增加了全局计数的 calloc
  65. * sc : 分配的个数
  66. * sz : 每个分配大小
  67. * : 返回分配内存的首地址
  68. */
  69. inline void*
  70. mg_calloc(size_t sc, size_t sz) {
  71. return mg_malloc(sc*sz);
  72. }
  73.  
  74. /*
  75. * 增加了计数的 realloc
  76. * ptr : 上一次分配的内存地址
  77. * sz : 待重新分配的内存大小
  78. * : 返回重新分配好的内存地址
  79. */
  80. void*
  81. mg_realloc(void* ptr, size_t sz) {
  82. if (!ptr) return mg_malloc(sz);
  83. return realloc(ptr, sz);
  84. }
  85.  
  86. /*
  87. * 增加了计数处理的内存 free
  88. * ptr : 上面函数返回地址的指针
  89. */
  90. inline void
  91. mg_free(void* ptr) {
  92. if (!ptr) return;
  93. --_mct;
  94. free(ptr);
  95. }

中间用了

  1. // 取消内置宏, 防止递归
  2. #undef malloc
  3. #undef calloc
  4. #undef realloc
  5. #undef free

这个主要为了解决 引用了 头文件 memglobal.h 会造成递归调用. Linux上还有一种思路, 不包含这个头文件

链接时候gcc 指定就可以.  但是 vs 是自动推导编译, 如果不引入它推导不出来. 后面就采用了上面通用的做法.

上面思路是, 先启动 全局内存监测功能, 再通过特殊宏,替代原先的申请和释放函数. 来达到目的.

测试文件 main.c

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include "memglobal.h"
  4.  
  5. /*
  6. * 内存全局计数, 检测内存是否越界
  7. */
  8. int main(int argc, char* argv[]) {
  9.  
  10. // 开启内存全局计数
  11. mg_start();
  12.  
  13. int *p = malloc();
  14.  
  15. p = calloc(, );
  16. *p = ;
  17.  
  18. puts("就这样!");
  19.  
  20. p = realloc(NULL, );
  21. puts("测试这样行!");
  22.  
  23. return ;
  24. }

测试运行结果如下

最终打印日志是

好. 到这里 关于内存全局检测的技巧解释和实现完毕. 很简单很好用.

重点是理解上面两种方式思路.  哈哈, 是不是发现  好神奇的内存泄露, 内存越界, 内存泄露监测也不过如此.

开发, 写代码很简单, 但化为生产力就很难了, 也许需要更多有能力的一起转换.

后记

  错误是难免, 欢迎吐槽交流, 拜~~. 希望早睡早起.

C基础 内存越界和内存监测的简单处理的更多相关文章

  1. c语言中较常见的由内存分配引起的错误_内存越界_内存未初始化_内存太小_结构体隐含指针

    1.指针没有指向一块合法的内存 定义了指针变量,但是没有为指针分配内存,即指针没有指向一块合法的内浅显的例子就不举了,这里举几个比较隐蔽的例子. 1.1结构体成员指针未初始化 struct stude ...

  2. JNI创建共享内存导致JVM terminated的问题解决(segfault,shared memory,内存越界,内存泄漏,共享内存)

    此问题研究了将近一个月,最终发现由于JNI不支持C中创建共享内存而导致虚拟机无法识别这块共享内存,造成内存冲突,最终虚拟机崩溃. 注意:JNI的C部分所使用的内存也是由JVM创建并管理的,所以C创建了 ...

  3. HSV做通道分离是出现的Vector内存越界错误

    vector<Mat> hsvSplit; //因为我们读取的是彩色图,直方图均衡化需要在HSV空间做 split(imgHSV, hsvSplit); equalizeHist(hsvS ...

  4. IOS上解决内存越界访问问题

    IOS经常会混合使用C代码,而在C中,对内存的读写是很频繁的操作. 其中,内存越界读写 unsigned char* p =(unsigned char*)malloc(10); unsigned c ...

  5. java 笔记(1)-—— JVM基础,内存数据,内存释放,垃圾回收,即时编译技术JIT,高精度类型

    1.java中5个存放数据的地方: (1).寄存器(Registers):位于CPU内部,是速度最快的存储区,但是数量和容量有限.在java中不能直接操作寄存器. (2).栈(Stack):栈位于通用 ...

  6. 使用PageHeap.EXE或GFlags.EXE检查内存越界错误 (转)

    2011-05-27 20:19 290人阅读 评论(0) 收藏 举报 microsoftdebuggingstructureoutputimagefile 必先利其器之一:使用PageHeap.EX ...

  7. JVM基础知识(1)-JVM内存区域与内存溢出

    JVM基础知识(1)-JVM内存区域与内存溢出 0. 目录 什么是JVM 运行时数据区域 HotSpot虚拟机对象探秘 OutOfMemoryError异常 1. 什么是JVM 1.1. 什么是JVM ...

  8. 使用PageHeap.EXE或GFlags.EXE检查内存越界错误

    必先利其器之一:使用PageHeap.EXE或GFlags.EXE检查内存越界错误 Article last modified on 2002-6-3 ------------------------ ...

  9. C++内存机制中内存溢出、内存泄露、内存越界和栈溢出的区别和联系

    当我们在用C++做底层驱动的时候,经常会遇到内存不足的警告,究其原因,往往是因为内存出现溢出,泄露或者越界等原因.那么他们之间有什么联系吗? 内存溢出(out of memory) 是指程序在申请内存 ...

随机推荐

  1. snmpwalk的报文检测

    1.先用nc起一个监听的端口,然后看报文是不是正确的: 注:nc是一个模拟各种网络协议的东西,模拟服务器.客户端等: 2.触发告警,让他发报文: 3.用nc模拟一个服务端,启动一个udp的端口163: ...

  2. Python fileinput模块详解

    Python的fileinput模块可以快速对一个或多个文件进行循环遍历. import fileinput for line in fileinput.input(): process(line) ...

  3. 什么是Docker?(6-12)

    关于什么是Docker,刚开始学的时候一脸懵X,这个东西到底是干嘛用的啊?偶然间在知乎上刷到一个比较通俗的解释: Docker就相当于一个容器,这个容器了不得了,它里面能搭好你项目需要的所有环境,并且 ...

  4. PKUWC2019 酱油记

    目录 PKUWC2019 酱油记 day0 Day1 Day2 Day3 Day4 PKUWC2019 酱油记 day0 早上从镇中出发到栎社机场,然后才了解到原来充电宝电脑是必须随身(原以为必须托运 ...

  5. BZOJ1012:[JSOI2008]最大数——题解

    https://www.lydsy.com/JudgeOnline/problem.php?id=1012 https://www.luogu.org/problemnew/show/P1198 现在 ...

  6. 洛谷 P1505 [国家集训队]旅游 解题报告

    P1505 [国家集训队]旅游 题目描述 \(\tt{Ray}\) 乐忠于旅游,这次他来到了\(T\)城.\(T\)城是一个水上城市,一共有 \(N\) 个景点,有些景点之间会用一座桥连接.为了方便游 ...

  7. HDU1166:敌兵布阵(线段树模板)

    敌兵布阵 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total Submis ...

  8. 使用 Rational AppScan 保证 Web 应用的安全性,第 1 部分: Web 安全与 Rational AppScan 入门

    前言 当今世界,Internet(因特网)已经成为一个非常重要的基础平台,很多企业都将应用架设在该平台上,为客户提供更为方便.快捷的服务支持.这些应用 在功能和性能上,都在不断的完善和提高,然而在非常 ...

  9. 轮廓算法的结果验证工具/How to validate the outline output

    因为轮廓算法的结果通过直接观察输出很难判断结果的正确性. 但是如果把输入和输出同时绘制出来,用眼睛判别则相对简单许多. 输入建筑的文件内容格式为,粗体格式为建筑高度: 10 110 5020 60 7 ...

  10. kvm增加硬盘挂载

    1.查询需要添加虚拟主机 [root@sz-kvm-110 images]# virsh list --all  Id    名称                         状态 ------- ...