C基础 内存统一入口
0赞引言 - malloc 引述
C标准中堆上内存入口就只有 malloc, calloc, realloc . 内存回收口是 free. 常见的一种写法是
struct person * per = malloc(sizoef(struct person)); if(NULL == ptr) { fprintf(stderr, "malloc struct person is error!"); // to do error thing ... ... } // 处理正常逻辑 ... // 回收 free(per);
特别是 if NULL == ptr 那些操作实在让人繁琐. 有点不爽, 构建了一组接口, 尝试一种方式来简便一下.
借鉴思路是 上层语言 new 的套路. 简单粗暴, 失败直接崩溃. 大体思路是
struct header * ptr = malloc(sz + sizeof(struct header)); // 检查内存分配的结果 if(NULL == ptr) { fprintf(stderr, "_header_get >%s:%d:%s< alloc error not enough memory start fail!\n", file, line, func); exit(EXIT_FAILURE); }
利用 exit 结束分配为NULL情况. 毕竟计算机一级内存不足, 一切运行对于软件层都已经是接近"未定义的边缘了"
参照资料 :云大大的skynet2 demohttps://github.com/cloudwu/skynet2/tree/master/skynet-src
前言 - 定义接口统一处理
处理的思路很简单, 主要是 提供一个内存申请的入口像new一样, 返回初始化的内存, 并且内存不足直接崩溃. 首先接口设计如下
scalloc.h
#ifndef _H_SIMPLEC_SCALLOC #define _H_SIMPLEC_SCALLOC #include// 释放sm_malloc_和sm_realloc_申请的内存, 必须配套使用 void sm_free_(void * ptr, const char * file, int line, const char * func); // 返回申请的一段干净的内存 void * sm_malloc_(size_t sz, const char * file, int line, const char * func); // 返回重新申请的内存, 只能和sm_malloc_配套使用 void * sm_realloc_(void * ptr, size_t sz, const char * file, int line, const char * func); /* * 释放申请的内存 * ptr : 申请的内存 */ #define sm_free(ptr) sm_free_(ptr, __FILE__, __LINE__, __func__) /* * 返回申请的内存, 并且是填充'\0' * sz : 申请内存的长度 */ #define sm_malloc(sz) sm_malloc_(sz, __FILE__, __LINE__, __func__) /* * 返回申请到num*sz长度内存, 并且是填充'\0' * num : 申请的数量 * sz : 申请内存的长度 */ #define sm_calloc(num, sz) sm_malloc_(num*sz, __FILE__, __LINE__, __func__) /* * 返回重新申请的内存 * ptr : 申请的内存 * sz : 申请内存的长度 */ #define sm_realloc(ptr, sz) sm_realloc_(ptr, sz, __FILE__, __LINE__, __func__) // 定义全局内存使用宏, 替换原有的malloc系列函数 #ifndef _SIMPLEC_SCALLOC_CLOSE # define free sm_free # define malloc sm_malloc # define calloc sm_calloc
# define realloc sm_realloc #endif #endif // !_H_SIMPLEC_SCALLOC
上面 sm_malloc sm_calloc sm_realloc sm_free 宏相比原先的四个函数, 多了几个编译宏参数, 方便以后查找问题.
_SIMPLEC_SCALLOC_CLOSE 头文件表示 当前是否替代老的 内存相关操作的入口.
这里扯一点, calloc 感觉是设计的失败.
#includevoid * calloc(size_t nmemb, size_t size); calloc() allocates memory for an array of nmemb elements of size bytes each and returns a pointer to the allocated memory. The memory is set to zero.
上面描述 相当于 calloc(nmemb, size) <=> malloc(nmemb*size) ; memset(ptr, 0, nmemb*size); 感觉好傻.
我么看看源码 在malloc.c 文件中实现 .
View Code
比较复杂, 从中就摘录下面 几行帮助理解
... /* size_t is unsigned so the behavior on overflow is defined. */ bytes = n * elem_size; ... return memset (mem, 0, sz); ... if (av != NULL) (void) mutex_unlock (&av->mutex); ...
实现起来很复杂, 主要围绕性能考虑, 重新套了一份内存申请的思路. 上面摘录的三点, 能够表明, 从功能上malloc 可以替代 calloc.
最后表明 glibc(gcc) 源码上是线程安全的.后面会分析上面接口的具体实现, 并测试个demo.
正文 - 开始实现, 运行demo
首先看具体实现,scalloc.c
#include#include #include // 标识枚举 typedef enum { HF_Alloc, HF_Free } header_e; // 每次申请内存的[16-24]字节额外消耗, 用于记录内存申请情况 struct header { header_e flag; // 当前内存使用的标识 int line; // 申请的文件行 const char * file; // 申请的文件名 const char * func; // 申请的函数名 }; // 内部使用的malloc, 返回内存会用'\0'初始化 void * sm_malloc_(size_t sz, const char * file, int line, const char * func) { struct header * ptr = malloc(sz + sizeof(struct header)); // 检查内存分配的结果 if(NULL == ptr) { fprintf(stderr, "_header_get >%s:%d:%s< alloc error not enough memory start fail!\n", file, line, func); exit(EXIT_FAILURE); } ptr->flag = HF_Alloc; ptr->line = line; ptr->file = file; ptr->func = func; memset(++ptr, 0, sz); return ptr; } // 得到申请内存的开头部分, 并检查 static struct header * _header_get(void * ptr, const char * file, int line, const char * func) { struct header * node = (struct header *)ptr - 1; // 正常情况直接返回 if(HF_Alloc != node->flag) { // 异常情况, 内存多次释放, 和内存无效释放 fprintf(stderr, "_header_get free invalid memony flag %d by >%s:%d:%s<\n", node->flag, file, line, func); exit(EXIT_FAILURE); } return node; } // 内部使用的realloc void * sm_realloc_(void * ptr, size_t sz, const char * file, int line, const char * func) { struct header * node , * buf; if(NULL == ptr) return sm_malloc_(sz, file, line, func); // 合理内存分割 node = _header_get(ptr, file, line, func); node->flag = HF_Free; // 构造返回内存信息 buf = realloc(node, sz + sizeof(struct header)); buf->flag = HF_Alloc; buf->line = line; buf->file = file; buf->func = func; return buf + 1; } // 内部使用的free, 每次释放都会打印日志信息 void sm_free_(void * ptr, const char * file, int line, const char * func) { if(NULL != ptr) { // 得到内存地址, 并且标识一下, 开始释放 struct header * node = _header_get(ptr, file, line, func); node->flag = HF_Free; free(node); } }
这里主要围绕 1 插入申请内存头
// 每次申请内存的[16-24]字节额外消耗, 用于记录内存申请情况 struct header { header_e flag; // 当前内存使用的标识 int line; // 申请的文件行 const char * file; // 申请的文件名 const char * func; // 申请的函数名 };
围绕2 在 malloc 时候 和 _header_get 得到头检查 时候, 直接exit.
思路很清晰基础, 假如这代码跑在64位机器上, 线上一个服务器, 运行时创建100000万个malloc对象 .
100000 * (4 + 4 + 8 +8)B / 1024 / 1024 = 2.288818359375 MB 的内存损耗. 还有一次取内存检查的性能损耗.
这些是可以接受的, 特殊时候可以通过打印的信息, 判断出内存调用出错的位置.
扯一点 这里用了枚举 方便和宏区分
// 标识枚举 typedef enum { HF_Alloc, HF_Free } header_e;
其实宏和枚举在C中基本一样, 只能人为的添加特殊规范, 约定二者区别. 宏用的太多, 复杂度会越来越大. 双刃剑.
下面我们测试一下 演示demomain.c
#include#include "scalloc.h" /* * 测试内存管理, 得到内存注册信息 */ int main(int argc, char * argv[]) { int * piyo = malloc(10); free(piyo); puts("start testing..."); // 简单测试一下 free(piyo); getchar(); return 0; }
演示结果
到这里 基本思路都已经介绍完毕了. 主要核心就是偷梁换柱.
后记 - ~○~
错误是难免的, 有问题再打补丁修复. 欢迎将这思路用在自己的项目构建中.