C基础 内存统一入口

引言  - 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 demo  https://github.com/cloudwu/skynet2/tree/master/skynet-src

前言 - 定义接口统一处理

  处理的思路很简单, 主要是 提供一个内存申请的入口像new一样, 返回初始化的内存, 并且内存不足直接崩溃. 首先接口设计如下

scalloc.h 

#ifndef _H_SIMPLEC_SCALLOC
#define _H_SIMPLEC_SCALLOC

#include <stdlib.h>

// 释放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 感觉是设计的失败.
#include <stdlib.h>

void * 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 文件中实现 .

void *
__libc_calloc (size_t n, size_t elem_size)
{
  mstate av;
  mchunkptr oldtop, p;
  INTERNAL_SIZE_T bytes, sz, csz, oldtopsize;
  void *mem;
  unsigned long clearsize;
  unsigned long nclears;
  INTERNAL_SIZE_T *d;

  /* size_t is unsigned so the behavior on overflow is defined.  */
  bytes = n * elem_size;
#define HALF_INTERNAL_SIZE_T \
  (((INTERNAL_SIZE_T) 1) << (8 * sizeof (INTERNAL_SIZE_T) / 2))
  if (__builtin_expect ((n | elem_size) >= HALF_INTERNAL_SIZE_T, 0))
    {
      if (elem_size != 0 && bytes / elem_size != n)
        {
          __set_errno (ENOMEM);
          return 0;
        }
    }

  void *(*hook) (size_t, const void *) =
    atomic_forced_read (__malloc_hook);
  if (__builtin_expect (hook != NULL, 0))
    {
      sz = bytes;
      mem = (*hook)(sz, RETURN_ADDRESS (0));
      if (mem == 0)
        return 0;

      return memset (mem, 0, sz);
    }

  sz = bytes;

  arena_get (av, sz);
  if (av)
    {
      /* Check if we hand out the top chunk, in which case there may be no
     need to clear. */
#if MORECORE_CLEARS
      oldtop = top (av);
      oldtopsize = chunksize (top (av));
# if MORECORE_CLEARS < 2
      /* Only newly allocated memory is guaranteed to be cleared.  */
      if (av == &main_arena &&
      oldtopsize < mp_.sbrk_base + av->max_system_mem - (char *) oldtop)
    oldtopsize = (mp_.sbrk_base + av->max_system_mem - (char *) oldtop);
# endif
      if (av != &main_arena)
    {
      heap_info *heap = heap_for_ptr (oldtop);
      if (oldtopsize < (char *) heap + heap->mprotect_size - (char *) oldtop)
        oldtopsize = (char *) heap + heap->mprotect_size - (char *) oldtop;
    }
#endif
    }
  else
    {
      /* No usable arenas.  */
      oldtop = 0;
      oldtopsize = 0;
    }
  mem = _int_malloc (av, sz);

  assert (!mem || chunk_is_mmapped (mem2chunk (mem)) ||
          av == arena_for_chunk (mem2chunk (mem)));

  if (mem == 0 && av != NULL)
    {
      LIBC_PROBE (memory_calloc_retry, 1, sz);
      av = arena_get_retry (av, sz);
      mem = _int_malloc (av, sz);
    }

  if (av != NULL)
    (void) mutex_unlock (&av->mutex);

  /* Allocation failed even after a retry.  */
  if (mem == 0)
    return 0;

  p = mem2chunk (mem);

  /* Two optional cases in which clearing not necessary */
  if (chunk_is_mmapped (p))
    {
      if (__builtin_expect (perturb_byte, 0))
        return memset (mem, 0, sz);

      return mem;
    }

  csz = chunksize (p);

#if MORECORE_CLEARS
  if (perturb_byte == 0 && (p == oldtop && csz > oldtopsize))
    {
      /* clear only the bytes from non-freshly-sbrked memory */
      csz = oldtopsize;
    }
#endif

  /* Unroll clear of <= 36 bytes (72 if 8byte sizes).  We know that
     contents have an odd number of INTERNAL_SIZE_T-sized words;
     minimally 3.  */
  d = (INTERNAL_SIZE_T *) mem;
  clearsize = csz - SIZE_SZ;
  nclears = clearsize / sizeof (INTERNAL_SIZE_T);
  assert (nclears >= 3);

  if (nclears > 9)
    return memset (d, 0, clearsize);

  else
    {
      *(d + 0) = 0;
      *(d + 1) = 0;
      *(d + 2) = 0;
      if (nclears > 4)
        {
          *(d + 3) = 0;
          *(d + 4) = 0;
          if (nclears > 6)
            {
              *(d + 5) = 0;
              *(d + 6) = 0;
              if (nclears > 8)
                {
                  *(d + 7) = 0;
                  *(d + 8) = 0;
                }
            }
        }
    }

  return mem;
}

比较复杂, 从中就摘录下面 几行帮助理解

 ...

  /* 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 <stdio.h>
#include <stdlib.h>
#include <string.h>

// 标识枚举
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中基本一样, 只能人为的添加特殊规范, 约定二者区别. 宏用的太多, 复杂度会越来越大. 双刃剑.

下面我们测试一下 演示demo main.c

#include <stdio.h>
#include "scalloc.h"

/*
 * 测试内存管理, 得到内存注册信息
 */
int main(int argc, char * argv[]) {
    int * piyo = malloc(10);
    free(piyo);

    puts("start testing...");

    // 简单测试一下
    free(piyo);

    getchar();
    return 0;
}

演示结果

到这里 基本思路都已经介绍完毕了. 主要核心就是偷梁换柱.

后记 - ~○~

  错误是难免的, 有问题再打补丁修复. 欢迎将这思路用在自己的项目构建中.

时间: 2024-10-09 18:13:04

C基础 内存统一入口的相关文章

【OC语法快览】四、基础内存管理

Basic Memory Management                                                           基础内存管理 If you're writing an application for Mac OS X, you have the option to enable garbage collection. In general, this means that you don't have to think about memory

Linux基础-----内存管理

free -m 查看内存大小 Mem:物理内存统计 total 物理内存大小 used  以使用内存(包含buffers;bached) free  空闲内存 shared 共享内存 buffers 缓冲(用于写操作) cached  缓存(用于读操作) -+ buffers/cached used (不包含buffers和cached;实际内存使用量) free  (包含buffers和cached;实际空闲内存) 根据以上分析,可以得出一下结论: 1.  实际可用内存大小: Free= Fr

OC基础 内存管理

OC基础  内存管理 我们所了解的c语言内存管理,如下: (1)c语言的内存分配:char *p = (char*)malloc(100*sizeof(char)); (2)c语言的内存释放:free(p); c语言内存管理存在的缺陷: (1)不释放就会造成内存泄露. (2)多次释放就会造成崩溃. (3)大型项目中多人对一块内存进行操作时,不能释放内存,因为不知道别人是否还在使用. oc语言的内存管理 1.内存管理 (1)引用计数. (2)内存管理的黄金法则:谁创建谁释放. 让我们来看一下测试例

提供统一入口--外观模式

在软件开发中,有时候为了完成一项较为复杂的功能,一个类需要和多个其它类进行交互,而这些需要交互的其它类通常作为一个完整的整体出现,由于涉及的类比较多,导致使用时代码较为复杂,此时,特别需要一个类似服务员一样的角色,由它复杂和多个业务类交互,使用这些业务类的类只需要和该类交互即可.外观模式,通过引入一个新的外观类来实现该功能,外观类充当了软件系统的"服务员",它为多个业务类的调用提供了统一的入口,简化了类与类之间的交互. 外观模式概述 外观模式要求一个子系统的外部与其内部的通信通过一个统

基础-内存

什么是内存 内存(Memory)是计算机中最重要的部件之一,它是程序与CPU进行沟通的桥梁.计算机中所有程序的运行都是在内存中进行的,因此内存对计算机的影响非常大,内存又被称为主存,其作用是存放 CPU 中的运算数据,以及与硬盘等外部存储设备交换的数据.只要计算机在运行中,CPU 就会把需要运算的数据调到主存中进行运算,当运算完成后CPU再将结果传送出来,主存的运行也决定了计算机的稳定运行. 内存的物理结构 在了解一个事物之前,你首先得先需要见过它,你才会有印象,才会有想要了解的兴趣,所以我们首

Java项目构建基础之统一日志收集

统一日志收集 日志是追踪错误定位问题的关键,尤其在生产环境中,需要及时修复热部署,不会提供开发者debug的环境,此时日志将会是最快解决问题的关键 Logback 关于logback的配置和介绍,可以参考官网或推荐博客glmapper的logback博客,logback-spring.xml配置文件 https://blog.csdn.net/xu_san_duo/article/details/80364600 配置 以下直接贴出配置信息,介绍信息可以直接参考备注 <?xml version=

iOS基础 ----- 内存管理

Objective-C 的内存管理方式有引用计数机制,垃圾回收机制,自动释放池.有alloc,就有释放.iOS应?程序出现Crash(闪退),90%的原因是因为内存问 题.在?个拥有数?个甚?是上百个类的?程?,查找内存问 题极其困难,学会内存管理,能帮我们减少出错的?率.内存问题体现在两个??:内存溢出.野指针异常. 引用计数器 在Xcode4.2及之后的版本中由于引入了ARC(Automatic Reference Counting)机制,程序编译时Xcode可以自动给你的代码添加内存释放代

java基础-内存分配

1.java运行时的数据区:程序计数器.方法区.虚拟机栈.本地方法栈.堆 ①.程序计数器:一块较小的内存空间,可看作当前线程所执行的字节码的行号指示器 ②.java虚拟机栈:与程序计数器一样,也是线程私有的,它的生命周期与线程相同,为虚拟机执行java方法服务(粗糙的讲就是栈内存) ③.本地方法栈:为虚拟机使用到的本地方法服务(栈内存) ④.堆:内存中最大的一块内存,被所有线程共享,在虚拟机启动时创建,目的是存放对象实例 ⑤.方法区:与堆一样,是各线程共享的区域,存储被加载的类信息.常量.静态变

OC基础:内存(进阶):retain.copy.assign的实现原理

遍历构造器的内存管理 a.遍历构造器方法内部使用autorelease释放对象 b.通过遍历构造器生成的对象.不用释放. 内存的管理总结 1.想占用某个对象的时候,要让它的引用计数器+1(retain操作) 2.当不想再占用某个对象的时候,要让它的引用计数器-1(release操作) 3.谁alloc谁release,遍历构造器使用autorelease 另:当一个属性遵循了协议的时候(该属性就是代理),这时使用内存组的assign修饰. 多态:父类指针 指向 子类对象 没有继承就没有多态 父类