程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> C基礎 內存統一入口,c基礎統一入口

C基礎 內存統一入口,c基礎統一入口

編輯:關於C語言

C基礎 內存統一入口,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; } 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;
}

演示結果

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

 

後記 - ~○~

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

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved