C基础 内存越界和内存监测的简单处理

来源:互联网 发布:linux内核剖析 pdf 编辑:程序博客网 时间:2024/05/16 00:40

前言  -  内存越界处理

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

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

那我们按照上面设计思路. 首先构建 接口文件 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(1, sz + 2 * _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(1, sz + 2 * _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(16);    mc_check(as);    // 内存越界了    //as[16] = 18;    //mc_check(as);    // 重新分配内存, 再次越界    as = mc_realloc(as, 15);    as[15] = 44;    mc_check(as);    free(as);    return 0;}
复制代码

 测试结果

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

 

正文 - 内存全局监测

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

包括内核层例如 文件描述符, 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__)// 全局内存计数, 系统第一次构造的时候为0static int _mct;#define _STR_MGTXT    "checkmem.log"// mg内存监测退出时候, 记录一些信息static void _mg_exit(void) {    if (_mct == 0) 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 voidmg_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(16);    p = calloc(12, 2);    *p = 154;    puts("就这样!");    p = realloc(NULL, 6);    puts("测试这样行!");        return 0;}
复制代码

 测试运行结果如下

最终打印日志是

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

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

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

 

本文由 whchina(江城老温)原创发布,转载请注明出处,江城老温 as a thinker。877313758

 

0 0