C基礎 記憶體統一入口

来源:http://www.cnblogs.com/life2refuel/archive/2016/08/06/5744370.html
-Advertisement-
Play Games

經常malloc, 然後if ptr == NULL 這種操作, 實在是受不了了. 抄了雲風的代碼,構建了一個 記憶體統一申請釋放的介面操作. ...


引言  - 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;
}
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 <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;
}

演示結果

到這裡 基本思路都已經介紹完畢了. 主要核心就是偷梁換柱.

 

後記 - ~○~

  錯誤是難免的, 有問題再打補丁修複. 歡迎將這思路用在自己的項目構建中.


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一、前言 Treeview控制項常用於遍歷本地文件信息,通常與Datagridview與ImageList搭配。ImageList控制項用於提供小圖片給TreeView控制項,DatagridView通常顯示TreeNode節點下文件及文件夾的信息。 效果圖: 二、代碼 初始化窗體: 初始化DataGri ...
  • 項目架構採用:Asp.Net MVC4.0 + EntityFramework6.0 code first + AutoMapper + Unity(IOC) + SqlServer2012 項目地址:www.xiaoboke.net 這個博客會不斷開發完善哦 歡迎大家去吐槽和提建議,一起學習,一起 ...
  • 使用自動載入和解析url的參數,實現調用到不同的控制器,實現了pathinfo模式和普通的url模式 文件結構: |--Controller |--Index |--Index.php |--Application.php Application.php \Controller\Index\Inde ...
  • 前言:在javaweb開發中自定義標簽的用處還是挺多的。今天和大家一起看自定義標簽是如何實現的。 1:什麼是標簽 標簽是一種XML元素,通過標簽可以使JSP頁面變得簡介易用,而且標簽具有很好的復用性。 2:自定義標簽的標簽庫主要的介面以及類的繼承實現關係圖 3:一步步實現自定義標簽 3.1:Tag接 ...
  • rest api的參數想即能支持application/x-www-form-urlencoded也能支持application/json方式傳參。 ...
  • 一、Hibernate中的關聯關係 1.1、單向一對多關聯關係 按照以下步驟配置hibernate中持久化類的一對多對象關聯: (1).持久化類添加關聯類的相關屬性及getter/setter方法。 (2).映射文件中建立該屬性和資料庫表欄位的映射信息。 比如班級對學生是一對多的關係,班級類Grad ...
  • scala除了方法外還支持函數,方法是對對象進行操作,而函數不是。(類型與java中靜態方法,媽蛋,好歹也寫過C和C++這還理解不深刻了)。除此之外,寫法一樣。 object add{ //指定返回值類型,返回的值不需要使用return指定,會取最後一個表達式的值。 def abs(x:Double ...
  • 冒泡排序 冒泡排序(Bubble Sort),是一種電腦科學領域的較簡單的排序演算法。 冒泡排序演算法的運作如下:(從後往前) l 依次比較相鄰的兩個元素,消除逆序(逆序是數學上的概念,是成對出現的,比如50,30就是一對逆序,所謂的消除逆序,就是大的放後面,小的放前面) l 這樣,一輪比較下來,最大 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...