C基础 内存越界和内存监测的简单处理
引言
突然感觉要出去走走了, 醒了后 刷完牙就在联系coding, 不知不觉到了 黄昏.
看看天, 打开灯. 又感觉到了 夜夜夜夜 .
13年到北京务工, 遇到一批批NB的同龄人物. 一块工作, 一块喜欢锻炼, 一块默默的学习.
从他(她)们身上发现一个事实.
假如我们一样聪明,
当你抱怨自己为什么努力了, 确还是 这么水的时候 ; 其实他(她)们在拼命. 而你只是在努力 ,
假如我们不一样聪明,
如果还不能开挂, 那会是怎么样精彩 x x x x.
前言 - 内存越界处理
我们先看设计图. 内存越界检查原理如下
上面原理是不是很简单. 而这恰恰是最通用的做法. 美的东西不负责. 美很重要.
那我们按照上面设计思路. 首先构建 接口文件 checkmem.h
#ifndef _H_MEMCHECK_CHECKMEM
#define _H_MEMCHECK_CHECKMEM #include <stddef.h> /*
* 对malloc进行的封装, 添加了边界检测内存块
* (inline 原本分_DEBUG有宏处理, 后面没加等于没用)
* sz : 申请内存长度
* : 返回得到的内存首地址
*/
extern inline void* mc_malloc(size_t sz); /*
* 对calloc进行封装, 添加边界检测内存块
* cut : 申请的个数
* sz : 每个的大小
*/
extern inline void* mc_calloc(size_t cut, size_t sz); /*
* 对relloc进行了封装, 同样添加了边间检测内存块
*/
extern inline void* mc_realloc(void* ptr, size_t sz); /*
* 对内存检测, 看是否出错, 出错直接打印错误信息
* 只能检测, check_* 得到的内存
*/
extern inline void mc_check(void* ptr); #endif // !_H_MEMCHECK_CHECKMEM
主要是对 malloc, calloc, realloc 进行添加尾部和头部的内存块处理. 就这么简单一步. 假如能看懂上面设计思路图.
这些代码都可以跳过了. 思路比代码重要. 好那我们继续展现实现部分. checkmem.c
#include "checkmem.h"
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h> // 控制台打印错误信息, fmt必须是双引号括起来的宏
#define CERR(fmt, ...) \
fprintf(stderr,"[%s:%s:%d][error %d:%s]" fmt "\r\n",\
__FILE__, __func__, __LINE__, errno, strerror(errno), ##__VA_ARGS__)
//控制台打印错误信息并退出, t同样fmt必须是 ""括起来的字符串常量
#define CERR_EXIT(fmt,...) \
CERR(fmt,##__VA_ARGS__),exit(EXIT_FAILURE) // 插入字节块的个数
#define _INT_CHECK (1<<4) /*
* 对malloc进行的封装, 添加了边界检测内存块
* sz : 申请内存长度
* : 返回得到的内存首地址
*/
inline void*
mc_malloc(size_t sz) {
// 头和尾都加内存检测块, 默认0x00
char* ptr = calloc(, sz + * _INT_CHECK);
if (NULL == ptr) {
CERR_EXIT("malloc sz + sizeof struct check is error!");
} //前四个字节保存 最后一个内存块地址 大小
size_t* iptr = (size_t*)ptr;
*iptr = sz + _INT_CHECK; return ptr + _INT_CHECK;
} /*
* 对calloc进行封装, 添加边界检测内存块
* cut : 申请的个数
* sz : 每个的大小
*/
inline void*
mc_calloc(size_t cut, size_t sz) {
return mc_malloc(cut*sz);
} /*
* 对relloc进行了封装, 同样添加了边间检测内存块
*/
inline void*
mc_realloc(void* ptr, size_t sz) {
// 先检测一下内存
mc_check(ptr); // 重新申请内存
char* cptr = (char*)ptr - _INT_CHECK;
char* nptr = calloc(, sz + * _INT_CHECK);
if (NULL == nptr) {
CERR_EXIT("realloc is error:%p.", ptr);
}
// 内存移动
size_t* bsz = (size_t*)cptr;
memcpy(nptr, cptr, *bsz < sz ? *bsz : sz);
*bsz = sz; free(cptr);
return nptr;
} // 检测内存是否错误, 错误返回 true, 在控制台打印信息
static void _iserror(char* s, char* e) {
while (s < e) {
if (*s) {
CERR_EXIT("Need to debug test!!! ptr is : (%p, %p).check is %d!",s, e, *s);
}
++s;
}
} /*
* 对内存检测, 看是否出错, 出错直接打印错误信息
* 只能检测, check_* 得到的内存
*/
inline void
mc_check(void* ptr) {
char *sptr = (char*)ptr - _INT_CHECK; //先检测头部
char* s = sptr + sizeof(size_t);
char* e = sptr + _INT_CHECK;
_iserror(s, e); //后检测尾部
size_t sz = *(size_t*)sptr;
s = sptr + sz;
e = s + _INT_CHECK;
_iserror(s, e);
}
代码实现都很中规中矩, 比较容易. 也就百行. 按照接口文件一个个看实现. 很容易学到开发中技巧. 提高实战技巧.
扯一点, C, C++ 老开发人员水平都比较高, 不喜欢写注释. 这个强烈推荐不是大牛的选手一定要多写注释.
不要扯 什么 <代码即注释> . 多写注释容易加深自己二次思考, 加快自己的成长. 不要和老开发人学这个 , 如果你跳槽, 遇到一个大项目
注释等价无, 你是什么感受. 为我们多留条后路, 多写注释.
好 看测试代码 main.c
#include <stdio.h>
#include <stdlib.h>
#include "checkmem.h" /*
* 演示一种检测内存越界的办法
* 添加上下限方式
*/
int main(int argc, char* argv[]) { // 实验步骤是, 是申请内存, 在操作内存
char* as = mc_malloc(); mc_check(as); // 内存越界了
//as[16] = 18;
//mc_check(as); // 重新分配内存, 再次越界
as = mc_realloc(as, );
as[] = ;
mc_check(as); free(as);
return ;
}
测试结果
到这里内存越界的思路和实现都已经完毕了.欢迎思考尝试.
正文 - 内存全局监测
内存全局检测思路更简单. 采用引用'计数方式'处理. 扯一点很多自动垃圾回收机制都采用了引用计数方式.
包括内核层例如 文件描述符, IPC 共享内存, 消息机制等. 先看接口 memglobal.h
#ifndef _H_MEMGLOBAL_MEMGLOBAL
#define _H_MEMGLOBAL_MEMGLOBAL #include <stddef.h>
#include <stdlib.h> /*
* 全局启动内存简单监测
*/
extern inline void mg_start(void); /*
* 增加的全局计数的 malloc
* sz : 待分配内存大小
* : 返回分配的内存首地址
*/
extern void* mg_malloc(size_t sz); /*
* 增加了全局计数的 calloc
* sc : 分配的个数
* sz : 每个分配大小
* : 返回分配内存的首地址
*/
extern inline void* mg_calloc(size_t sc, size_t sz); /*
* 增加了计数的 realloc
* ptr : 上一次分配的内存地址
* sz : 待重新分配的内存大小
* : 返回重新分配好的内存地址
*/
extern void* mg_realloc(void* ptr, size_t sz); /*
* 增加了计数处理的内存 free
* ptr : 上面函数返回地址的指针
*/
extern inline void mg_free(void* ptr); // 在测试模式下开启 全局内存使用计数
#if defined(_DEBUG)
# define malloc mg_malloc
# define calloc mg_calloc
# define realloc mg_realloc
# define free mg_free
#else
# define malloc malloc
# define calloc calloc
# define realloc realloc
# define free free
#endif #endif // !_H_MEMGLOBAL_MEMGLOBAL
还是比较优美的. 再看 memglobal.c
#include "memglobal.h"
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h> // 取消内置宏, 防止递归
#undef malloc
#undef calloc
#undef realloc
#undef free // 控制台打印错误信息, fmt必须是双引号括起来的宏
#define IOERR(io, fmt, ...) \
fprintf(io,"[%s:%s:%d][error %d:%s]" fmt "\r\n",\
__FILE__, __func__, __LINE__, errno, strerror(errno), ##__VA_ARGS__) // 全局内存计数, 系统第一次构造的时候为0
static int _mct; #define _STR_MGTXT "checkmem.log" // mg内存监测退出时候, 记录一些信息
static void _mg_exit(void) {
if (_mct == ) return; // 先打印信息到控制台
IOERR(stderr, "Detect memory leaks _mct = %d!!", _mct); //输出到文件
FILE* txt = fopen(_STR_MGTXT, "a");
if (txt == NULL) {
IOERR(stderr, "fopen " _STR_MGTXT " a is error!");
return;
}
IOERR(txt, "Detect memory leaks _mct = %d!!", _mct);
fclose(txt);
} /*
* 全局启动内存简单监测
*/
inline void
mg_start(void) {
// 注册退出监测事件
atexit(_mg_exit);
} /*
* 增加的全局计数的 malloc
* sz : 待分配内存大小
* : 返回分配的内存首地址
*/
void*
mg_malloc(size_t sz) {
void* ptr = malloc(sz);
if (!ptr) return NULL;
++_mct;
memset(ptr, 0x00, sz);
return ptr;
} /*
* 增加了全局计数的 calloc
* sc : 分配的个数
* sz : 每个分配大小
* : 返回分配内存的首地址
*/
inline void*
mg_calloc(size_t sc, size_t sz) {
return mg_malloc(sc*sz);
} /*
* 增加了计数的 realloc
* ptr : 上一次分配的内存地址
* sz : 待重新分配的内存大小
* : 返回重新分配好的内存地址
*/
void*
mg_realloc(void* ptr, size_t sz) {
if (!ptr) return mg_malloc(sz);
return realloc(ptr, sz);
} /*
* 增加了计数处理的内存 free
* ptr : 上面函数返回地址的指针
*/
inline void
mg_free(void* ptr) {
if (!ptr) return;
--_mct;
free(ptr);
}
中间用了
// 取消内置宏, 防止递归
#undef malloc
#undef calloc
#undef realloc
#undef free
这个主要为了解决 引用了 头文件 memglobal.h 会造成递归调用. Linux上还有一种思路, 不包含这个头文件
链接时候gcc 指定就可以. 但是 vs 是自动推导编译, 如果不引入它推导不出来. 后面就采用了上面通用的做法.
上面思路是, 先启动 全局内存监测功能, 再通过特殊宏,替代原先的申请和释放函数. 来达到目的.
测试文件 main.c
#include <stdio.h>
#include <stdlib.h>
#include "memglobal.h" /*
* 内存全局计数, 检测内存是否越界
*/
int main(int argc, char* argv[]) { // 开启内存全局计数
mg_start(); int *p = malloc(); p = calloc(, );
*p = ; puts("就这样!"); p = realloc(NULL, );
puts("测试这样行!"); return ;
}
测试运行结果如下
最终打印日志是
好. 到这里 关于内存全局检测的技巧解释和实现完毕. 很简单很好用.
重点是理解上面两种方式思路. 哈哈, 是不是发现 好神奇的内存泄露, 内存越界, 内存泄露监测也不过如此.
开发, 写代码很简单, 但化为生产力就很难了, 也许需要更多有能力的一起转换.
后记
错误是难免, 欢迎吐槽交流, 拜~~. 希望早睡早起.
C基础 内存越界和内存监测的简单处理的更多相关文章
- c语言中较常见的由内存分配引起的错误_内存越界_内存未初始化_内存太小_结构体隐含指针
1.指针没有指向一块合法的内存 定义了指针变量,但是没有为指针分配内存,即指针没有指向一块合法的内浅显的例子就不举了,这里举几个比较隐蔽的例子. 1.1结构体成员指针未初始化 struct stude ...
- JNI创建共享内存导致JVM terminated的问题解决(segfault,shared memory,内存越界,内存泄漏,共享内存)
此问题研究了将近一个月,最终发现由于JNI不支持C中创建共享内存而导致虚拟机无法识别这块共享内存,造成内存冲突,最终虚拟机崩溃. 注意:JNI的C部分所使用的内存也是由JVM创建并管理的,所以C创建了 ...
- HSV做通道分离是出现的Vector内存越界错误
vector<Mat> hsvSplit; //因为我们读取的是彩色图,直方图均衡化需要在HSV空间做 split(imgHSV, hsvSplit); equalizeHist(hsvS ...
- IOS上解决内存越界访问问题
IOS经常会混合使用C代码,而在C中,对内存的读写是很频繁的操作. 其中,内存越界读写 unsigned char* p =(unsigned char*)malloc(10); unsigned c ...
- java 笔记(1)-—— JVM基础,内存数据,内存释放,垃圾回收,即时编译技术JIT,高精度类型
1.java中5个存放数据的地方: (1).寄存器(Registers):位于CPU内部,是速度最快的存储区,但是数量和容量有限.在java中不能直接操作寄存器. (2).栈(Stack):栈位于通用 ...
- 使用PageHeap.EXE或GFlags.EXE检查内存越界错误 (转)
2011-05-27 20:19 290人阅读 评论(0) 收藏 举报 microsoftdebuggingstructureoutputimagefile 必先利其器之一:使用PageHeap.EX ...
- JVM基础知识(1)-JVM内存区域与内存溢出
JVM基础知识(1)-JVM内存区域与内存溢出 0. 目录 什么是JVM 运行时数据区域 HotSpot虚拟机对象探秘 OutOfMemoryError异常 1. 什么是JVM 1.1. 什么是JVM ...
- 使用PageHeap.EXE或GFlags.EXE检查内存越界错误
必先利其器之一:使用PageHeap.EXE或GFlags.EXE检查内存越界错误 Article last modified on 2002-6-3 ------------------------ ...
- C++内存机制中内存溢出、内存泄露、内存越界和栈溢出的区别和联系
当我们在用C++做底层驱动的时候,经常会遇到内存不足的警告,究其原因,往往是因为内存出现溢出,泄露或者越界等原因.那么他们之间有什么联系吗? 内存溢出(out of memory) 是指程序在申请内存 ...
随机推荐
- python函数调用关系图(python call graph)
由于要重构项目的部分代码,要整理好主要的函数调用关系,不想自己看代码慢慢画出结构,想找出一种通用的,节省人力的方法得出函数间的调用关系图,于是发现以下几个工具.(内网没装好graphviz,还没真正用 ...
- 【刷题】BZOJ 3140 [Hnoi2013]消毒
Description 最近在生物实验室工作的小T遇到了大麻烦. 由于实验室最近升级的缘故,他的分格实验皿是一个长方体,其尺寸为abc,a.b.c 均为正整数.为了实验的方便,它被划分为abc个单位立 ...
- 消息传递 树形DP
非常妙的树形DP:由于n很小,我们可以枚举每一个点作为第一个节点,计算其时间花费 那么问题就转化为对于给点节点求花费时间. 通过观察,显然我们会发现先传给花费时间多的人更加合算,因为这样可以最大限度的 ...
- BZOJ2844:albus就是要第一个出场——题解
https://www.lydsy.com/JudgeOnline/problem.php?id=2844 已知一个长度为n的正整数序列A(下标从1开始), 令 S = { x | 1 <= x ...
- CF17E:Palisection——题解
https://vjudge.net/problem/CodeForces-17E http://codeforces.com/problemset/problem/17/E 题目大意:给一个长度为n ...
- 无序数组中第Kth大的数
题目:找出无序数组中第Kth大的数,如{63,45,33,21},第2大的数45. 输入: 第一行输入无序数组,第二行输入K值. 该是内推滴滴打车时(2017.8.26)的第二题,也是<剑指of ...
- UVA.10192 Vacation (DP LCS)
UVA.10192 Vacation (DP LCS) 题意分析 某人要指定旅游路线,父母分别给出了一系列城市的旅游顺序,求满足父母建议的最大的城市数量是多少. 对于父母的建议分别作为2个子串,对其做 ...
- mysql 集群+主从同步
SQL节点: 给上层应用层提供sql访问. 管理节点(MGM): 管理整个集群. 启动,关闭集群. 通过ndb_mgmd命令启动集群 存储/数据节点: 保存cluster中的数据. 数据节点,可以 ...
- scanf函数用法小记
By francis_hao Aug 26,2017 scanf – 输入格式转换 概述 #include <stdio.h>int scanf(const char *fo ...
- HDU多校(Distinct Values)
Problem Description Chiaki has an array of n positive integers. You are told some facts about the ar ...