跨平台c開發庫tbox:內存庫使用詳解

  • 2019 年 12 月 4 日
  • 筆記

內存整體架構

TBOX的內存管理模型,參考了linux kernel的內存管理機制,並在其基礎上做了一些改進和優化。

內存池架構

大塊內存池:large_pool

整個內存分配的最底層,都是基於large_pool的大塊內存分配池,類似於linux的基於page的分配管理,不過有所不同的是,large_pool並沒有像linux那樣使用buddy算法進行(2^N)*page進行分配,這樣如果需要2.1m的內存,需要分配4m的內存塊,這樣粒度太大,非常浪費。

因此large_pool內部採用N*page的基於page_size為最小粒度進行分配,因此每次分配頂多浪費不到一頁的空間。

而且如果需要的內存不到整頁,剩下的內存也會一併返回給上層,如果上層需要(比如small_pool),可以充分利用這多餘的部分內存空間,使得內存利用率達到最優化。

而且根據tb_init實際傳入的參數需求,large_pool有兩種模式:

  1. 直接使用系統內存分配接口將進行大塊內存的分配,並用雙鏈維護,這種比較簡單,就不多說了。
  2. 在一大塊連續內存上進行統一管理,實現內存分配。

具體使用哪種方式,根據應用需求,一般的應用只需要使用方式1就行了,這個時候tb_init傳tb_null就行了,如果是嵌入式應用,需要管理有限的一塊內存空間,這個時候可以使用方式2, tb_init傳入指定內存空間地址和大小。

這裡就主要看下方式2的large_pool的內存結構(假設頁大小是4KB):

     --------------------------------------------------------------------------      |                                     data                                 |       --------------------------------------------------------------------------                                           |       --------------------------------------------------------------------------      | head | 4KB | 16KB | 8KB | 128KB | ... | 32KB |       ...       |  4KB*N  |       --------------------------------------------------------------------------

由於large_pool主要用於大塊分配,而超小塊的分配在上層small_pool中已經被分流掉了,所以這個應用中,large_pool不會太過頻繁的分配,所以碎片量不會太大,為了進一步減少碎片的產生,在free時候都會對下一個鄰近的空閑塊進行合併。而malloc在分配當前空閑塊空間不夠的情況下,也會嘗試對下一個鄰近空閑塊進行合併。

由於每個內存塊都是鄰近挨着的,也沒用雙鏈維護,沒有內存塊,都有個塊頭,合併過程僅僅只是改動內存塊頭部的size字段,這樣的合併不會影響效率。

由於沒像buddy算法那樣,用雙鏈維護空閑內存,雖然節省了鏈表維護的空間和時間,但是每次分配內存都要順序遍歷所有塊,來查找空閑的內存,這樣的效率實在太低了,為了解決這個問題,large_pool內部針對不同級別的塊,進行了預測,每次free或者malloc的時候,如果都會把當前和鄰近的空閑快,緩存到對應級別的預測池裏面去,具體的分級如下:

     --------------------------------------      | >0KB :      4KB       | > 0*page     |      |-----------------------|--------------      | >4KB :      8KB       | > 1*page     |      |-----------------------|--------------      | >8KB :    12-16KB     | > 2*page     |      |-----------------------|--------------      | >16KB :   20-32KB     | > 4*page     |      |-----------------------|--------------      | >32KB :   36-64KB     | > 8*page     |      |-----------------------|--------------      | >64KB :   68-128KB    | > 16*page    |      |-----------------------|--------------      | >128KB :  132-256KB   | > 32*page    |      |-----------------------|--------------      | >256KB :  260-512KB   | > 64*page    |      |-----------------------|--------------      | >512KB :  516-1024KB  | > 128*page   |      |-----------------------|--------------      | >1024KB : 1028-...KB  | > 256*page   |       --------------------------------------

由於通常不會分配太大塊的內存,因此只要能夠預測1m內存,就足夠,而對於>1m的內存,這裡也單獨加了一個預測,來應對偶爾的超大塊分配,並且使得整體分配流程更加的統一。

如果當前級別的預測塊不存在,則會到下一級別的預測塊中查找,如果都找不到,才會去遍歷整個內存池。

實際測試下,每個塊的預測成功基本都在95%以上,也就說大部分情況下,分配效率都是維持在O(1)級別的。

小塊內存池:small_pool

小塊內存分配池

在上層每次調用malloc進行內存分配的時候,會去判斷需要多大的內存,如果這個內存超過或者等於一頁,則會直接從large_pool進行分配,如果小於一頁,則會優先通過small_pool進行分配,small_pool針對小塊的內存進行了高速緩存,並優化了空間管理和分配效率。

由於程序大部分情況下,都在使用小塊內存,因此small_pool對內存的分配做了很大的分流,使得large_pool承受的壓力減小,碎片量減少很多,而small_pool內部由於都是由fixed_pool來對固定大小的內存進行管理,是不會存在外部碎片的。而小塊內存的粒度本身就很小,所以內部碎片量也相當少。

small_pool中的fixed_pool,就像是linux kernel中的slub,在small_pool中總共有12級別的fixed_pool,每個級別分別管理一種固定大小的內存塊,具體級別如下:

     --------------------------------------      |    fixed pool: 16B    |  1-16B       |      |--------------------------------------|      |    fixed pool: 32B    |  17-32B      |      |--------------------------------------|      |    fixed pool: 64B    |  33-64B      |      |--------------------------------------|      |    fixed pool: 96B*   |  65-96B*     |      |--------------------------------------|      |    fixed pool: 128B   |  97-128B     |      |--------------------------------------|      |    fixed pool: 192B*  |  129-192B*   |      |--------------------------------------|      |    fixed pool: 256B   |  193-256B    |      |--------------------------------------|      |    fixed pool: 384B*  |  257-384B*   |      |--------------------------------------|      |    fixed pool: 512B   |  385-512B    |      |--------------------------------------|      |    fixed pool: 1024B  |  513-1024B   |      |--------------------------------------|      |    fixed pool: 2048B  |  1025-2048B  |      |--------------------------------------|      |    fixed pool: 3072B* |  2049-3072B* |       -------------------------------------- 

其中 96B, 192B,384B,3072B並不是按2的整數冪大小,這麼做主要是為了更加有效的利用小塊內存的空間減少內部碎片。

固定塊內存池:fixed_pool

顧名思義,fixed_pool就是用來管理固定大小的內存分配的,相當於linux中slub,而fixed_pool中又由多個slot組成,每個slot負責一塊連續的內存空間,管理部分內存塊的管理,類似linux中的slab, 每個slot由雙鏈維護,並且參考linux的管理機制,分為三種slot管理方式:

  1. 當前正在分配的slot
  2. 部分空閑slots鏈表
  3. 完全full的slots鏈表

具體結構如下:

    current:           --------------          |              |       --------------    |      |     slot     |<--      |--------------|      ||||||||||||||||      |--------------|      |              |      |--------------|      |              |      |--------------|      ||||||||||||||||      |--------------|      ||||||||||||||||      |--------------|      |              |       --------------        partial:         --------------       --------------               --------------      |     slot     | <=> |     slot     | <=> ... <=> |     slot     |      |--------------|     |--------------|             |--------------|      ||||||||||||||||     |              |             |              |      |--------------|     |--------------|             |--------------|      |              |     ||||||||||||||||             |              |      |--------------|     |--------------|             |--------------|      |              |     ||||||||||||||||             ||||||||||||||||      |--------------|     |--------------|             |--------------|      ||||||||||||||||     ||||||||||||||||             |              |      |--------------|     |--------------|             |--------------|      ||||||||||||||||     |              |             |              |      |--------------|     |--------------|             |--------------|      |              |     |              |             ||||||||||||||||      --------------       --------------               --------------        full:         --------------       --------------               --------------      |     slot     | <=> |     slot     | <=> ... <=> |     slot     |      |--------------|     |--------------|             |--------------|      ||||||||||||||||     ||||||||||||||||             ||||||||||||||||      |--------------|     |--------------|             |--------------|      ||||||||||||||||     ||||||||||||||||             ||||||||||||||||      |--------------|     |--------------|             |--------------|      ||||||||||||||||     ||||||||||||||||             ||||||||||||||||      |--------------|     |--------------|             |--------------|      ||||||||||||||||     ||||||||||||||||             ||||||||||||||||      |--------------|     |--------------|             |--------------|      ||||||||||||||||     ||||||||||||||||             ||||||||||||||||      |--------------|     |--------------|             |--------------|      ||||||||||||||||     ||||||||||||||||             ||||||||||||||||       --------------       --------------               --------------

具體的分配算法

  1. 如果當前slot中還有空閑的塊,優先從當前slot進行分配
  2. 如果當前slot中沒有空閑塊,則把這個slot放到full鏈表中去
  3. 從部分空閑slot鏈表中,挑一個空閑的slot進行分配,並把它設為當前分配狀態。

具體的釋放算法

  1. 釋放後如果這個slot完全空閑了,並且不是正在分配的slot,則把整個slot釋放掉,這樣既可以保證有一個可以分配的slot之外,還極大的降低了內存使用,也避免某些情況下頻繁的釋放分配slot。
  2. 如果釋放的slot屬於full鏈表並且變為了部分空閑,則把這個slot移到部分空閑slot鏈表中去。

額外要提一下的是

large_pool每次分配一塊空間給一個slot的時候,殘留下來的部分剩餘空間(<1*page), 也能直接返回給slot,讓slot充分利用這部分數據,這樣可以可以切分出更多的內存塊。

例如:

fixed_pool每次增長一個包含256個32B內存塊的slot(需要8192B大小+16B內部數據維護大小),其實在用large_pool分配的時候,需要8208B的大小,由於需要按頁對齊(4KB),實際分配卻佔用了8192+4096: 12288B的大小的空間。

但是large_pool支持把所有空間數據一併返回給上層,這樣slot其實獲取到了一個12288B大小的內存,並且也知道其實際大小為:12288B,因此實際切分了(12288-(32B的slot內部維護數據))/32也就是383個內存塊。

多維護了127個內存塊,充分把large_pool的內部碎片也利用上了,進一步增加了內存利用率。

fixed_pool中的slot

雖然類比與linux中的slab,但是其數據結構卻跟slab不太一樣,它並沒有像slab那樣,對每個空閑小塊都用鏈表維護,而是直接用位段來維護是否空閑的信息,這樣更加節省內存,而且通過優化算法,其分配效率和slab幾乎一樣。

在fixed_pool的slot的頭部,專門有一小塊獨立的數據,用於維護每個小塊的空閑信息,每個塊只佔用一比特位的信息,來判斷這個塊是否空閑,由於沒有內存塊都是固定大小的,所以比特位的位置定位,完全可以通過索引計算得到。

而且每次釋放和分配,都會去緩存一個雙字大小的位信息端,來預測下一次的分配,由於是雙字大小,總共有32個比特位,所以每次緩存,最多可以預測鄰近32個內存塊。因此大部分情況下,預測成功率一直都是>98%的,分配效率都維持在O(1),比起large_pool的預測率還高很多,所以small_pool對large_pool的分流,還在一定程度上,進一步提高了內存分配效率。

而就算很倒霉,沒預測成功,slot的順序遍歷來查找空閑快的算法,也相當高效,完全是高度優化的,下面就詳細描述下。

slot的順序遍歷分配算法優化

我們這裡主要用到了gcc的幾個內置函數:

  1. __builtin_clz:計算32位整數前導0的個數
  2. __builtin_ctz:計算32位整數後置0的個數
  3. __builtin_clzll:計算64位整數前導0的個數
  4. __builtin_ctzll:計算64位整數後置0的個數

其實這四個類似,我們這裡就拿第一說明好了,為什麼要使用__builtin_clz呢?其實就是為了在一個32位端裏面,快速查找某個空閑位的索引,這樣就能快速定位某個空閑塊的位置了。

比如有一個32位的位段信息整數:x,計算對應空閑位0的索引,主需要:__builtin_clz(~x)

簡單吧,由於__builtin_clz這些內置函數,gcc用彙編針對不同平台高度優化過的,計算起來相當的快,那如果不是gcc的編譯器怎麼辦呢?

沒關係,我們可以自己用c實現個優化版本的,當然完全可以彙編繼續優化,這裡就先給個c的實現:

    static __tb_inline__ tb_size_t tb_bits_cl0_u32_be_inline(tb_uint32_t x)      {          // check          tb_check_return_val(x, 32);            // done          tb_size_t n = 31;          if (x & 0xffff0000) { n -= 16;  x >>= 16;   }          if (x & 0xff00)     { n -= 8;   x >>= 8;    }          if (x & 0xf0)       { n -= 4;   x >>= 4;    }          if (x & 0xc)        { n -= 2;   x >>= 2;    }          if (x & 0x2)        { n--;                  }          return n;      }

說白了,就是每次對半開,來減少判斷次數,比起每次一位一位的枚舉遍歷,這種已經是相當高效了,更何況還有__builtin_clz呢。

接下來就看下具體的遍歷過程:

  1. 按4/8位元組對齊位段的起始地址
  2. 每次按4/8位元組遍歷位段數據,遍歷過程利用cpu cache的大小,針對性的做循環展開,來優化性能。
  3. 通過判斷 !(x + 1) 來快速過濾 0xffffffff 這些已經滿了的位段,進一步提高遍歷效率。
  4. 如果某個位段不是0xffffffff,則通過__builtin_clz(~x)計算實際的空閑塊索引,並進行實際的分配。
  5. 最後如果這個的32位的位段沒有被分配滿,可以把它進行緩存,來為下次分配做預測。

字符串內存池:string_pool

講到這,TBOX的內存池管理模型,基本算是大概講完了,這裡就簡單提下string_pool,即:字符串池

string_pool主要針對上層應用而言的,針對某些頻繁使用小型字符串,並且重複率很高的模塊,就可以通過string_pool進行優化,進一步減少內存使用,string_pool內部通過引用計數+哈希表維護,針對相同的字符串只保存一份。

例如可以用於cookies中字符串維護、http中header部分的字符串維護等等。。

切換全局的內存分配器

tbox的默認內存分配,是完全基於自己的內存池架構,支持內存的快速分配,和對碎片的優化,並且支持各種內存泄露、溢出檢測。

如果不想用tbox內置的默認內存分配管理,也可以靈活切換到其他分配模式,因為tbox現在已經完全支持allocator架構, 只要在init階段傳入不同的分配器模型,就能快速切換分配模式,例如:

默認內存分配器

tbox默認的初始化採用了默認的tbox內存管理,他會默認啟用內存池維護、碎片優化、內存泄露溢出檢測等所有特性。

tb_init(tb_null, tb_null);

上面的初始化等價於:

tb_init(tb_null, tb_default_allocator(tb_null, 0));

默認的分配器,通常情況下都會直接調用系統malloc使用系統原生native內存,只是在此基礎上多了層內存管理和內存檢測支持,如果想要在一塊連續內存上完全託管,可以使用下面的方式:

tb_init(tb_null, tb_default_allocator((tb_byte_t*)malloc(300 * 1024 * 1024), 300 * 1024 * 1024));

靜態內存分配器

我們也可以直接採用一整塊靜態buffer上進行維護,啟用內存泄露溢出檢測等所有特性,這個跟tb_default_allocator的區別就是, 這個分配器比較輕量,內部的數據結構簡單,佔用內存少,適合低資源環境,比如在一些嵌入式環境,用這個分配器資源利用率更高些。

!> 但是這個allocator不支持碎片優化,容易產生碎片。

tb_init(tb_null, tb_static_allocator((tb_byte_t*)malloc(300 * 1024 * 1024), 300 * 1024 * 1024));

原生內存分配器

完全使用系統native內存分配,內部不做任何處理和數據維護,所有特性依賴系統環境,所以內存池和內存檢測等特性也是不支持的,相當於直接透傳給了malloc等系統分配接口。

用戶可以根據自己的需要,如果不想使用tbox內置的內存池維護,就可以使用此分配器。

tb_init(tb_null, tb_native_allocator());

虛擬內存分配器

v1.6.4版本之後,tbox新提供了一種分配器類型:虛擬內存分配器,主要用來分配一些超大塊的內存。

通常,用戶並不需要它,因為tbox默認的內存分配器內部自動會對超大塊的內存塊,切換到虛擬內存池去分配,不過如果用戶想要強制切換到虛擬內存分配,也可以通過下面的方式切換使用:

tb_init(tb_null, tb_virtual_allocator());

自定義內存分配器

如果覺得這些分配器還是不夠用,可以自定義自己的內存分配器,讓tbox去使用,自定義的方式也很簡單,這裡拿tb_native_allocator的實現代碼為例:

static tb_pointer_t tb_native_allocator_malloc(tb_allocator_ref_t allocator, tb_size_t size __tb_debug_decl__)  {      // trace      tb_trace_d("malloc(%lu) at %s(): %lu, %s", size, func_, line_, file_);        // malloc it      return malloc(size);  }  static tb_pointer_t tb_native_allocator_ralloc(tb_allocator_ref_t allocator, tb_pointer_t data, tb_size_t size __tb_debug_decl__)  {      // trace      tb_trace_d("realloc(%p, %lu) at %s(): %lu, %s", data, size, func_, line_, file_);        // realloc it      return realloc(data, size);  }  static tb_bool_t tb_native_allocator_free(tb_allocator_ref_t allocator, tb_pointer_t data __tb_debug_decl__)  {      // trace      tb_trace_d("free(%p) at %s(): %lu, %s", data, func_, line_, file_);        // free it      return free(data);  }

然後我們初始化下咱們自己實現的native分配器:

tb_allocator_t myallocator    = {0};  myallocator.type              = TB_ALLOCATOR_NATIVE;  myallocator.malloc            = tb_native_allocator_malloc;  myallocator.ralloc            = tb_native_allocator_ralloc;  myallocator.free              = tb_native_allocator_free;

是不是很簡單,需要注意的是,上面的__tb_debug_decl__宏裏面聲明了一些debug信息,例如_file, _func, _line等內存分配時候記錄的信息, 你可以在debug的時候打印出來,做調試,也可以利用這些信息自己去處理一些高級的內存檢測操作,但是這些在release下,是不可獲取的

所以處理的時候,需要使用__tb_debug__宏,來分別處理。。

將myallocator傳入tb_init接口後,之後 tb_malloc/tb_ralloc/tb_free/... 等所有tbox內存分配接口都會切到新的allocator上進行分配。。

tb_init(tb_null, &myallocator);

當然如果想直接從一個特定的allocator上進行分配,還可以直接調用allocator的分配接口來實現:

tb_allocator_malloc(&myallocator, 10);  tb_allocator_ralloc(&myallocator, data, 100);  tb_allocator_free(&myallocator, data);

內存分配接口

數據分配接口

此類接口可以直接分配內存數據,不過返回的是tb_pointer_t類型數據,通過用戶需要自己做類型強轉才能訪問。

!> 其中malloc0這種後綴帶0字樣的接口,分配的內存會自動做內存清0操作。

tb_free(data)  tb_malloc(size)  tb_malloc0(size)  tb_nalloc(item, size)  tb_nalloc0(item, size)  tb_ralloc(data, size)                       

字符串分配接口

tbox也提供了字符串類型的便捷分配,操作的數據類型直接就是tb_char_t*,省去了額外的強轉過程。

tb_malloc_cstr(size)  tb_malloc0_cstr(size)  tb_nalloc_cstr(item, size)  tb_nalloc0_cstr(item, size)  tb_ralloc_cstr(data, size)                  

位元組數據分配接口

這個也是數據分配接口,唯一的區別就是,默認做了tb_byte_t*類型的強轉處理,訪問數據讀寫訪問。

tb_malloc_bytes(size)  tb_malloc0_bytes(size)  tb_nalloc_bytes(item, size)  tb_nalloc0_bytes(item, size)  tb_ralloc_bytes(data, size)                 

struct結構數據分配接口

如果要分配一些struct數據,那麼此類接口自帶了struct類型強轉處理。

tb_malloc_type(type)  tb_malloc0_type(type)  tb_nalloc_type(item, type)  tb_nalloc0_type(item, type)  tb_ralloc_type(data, item, type)      

使用方式如下:

typedef struct __xxx_t  {      tb_int_t dummy;    }xxx_t;    xxx_t* data = tb_malloc0_type(xxx_t);  if (data)  {      data->dummy = 0;      tb_free(data);  }

可以看到,我們省去了類型轉換過程,所以這是個提供一定便利性的輔助接口。

地址對齊數據分配接口

如果我們有時候要求分配出來的內存數據地址,必須是按照指定大小對齊過的,就可以使用此類接口:

tb_align_free(data)  tb_align_malloc(size, align)  tb_align_malloc0(size, align)  tb_align_nalloc(item, size, align)  tb_align_nalloc0(item, size, align)  tb_align_ralloc(data, size, align) 

例如:

tb_pointer_t data = tb_align_malloc(1234, 16);

實際分配出來的data數據地址是16位元組對齊的。

如果是按8位元組對齊的內存數據分配,也可以通過下面的接口來分配,此類接口對64bits系統上做了優化,並沒做什麼特殊處理:

#if TB_CPU_BIT64  #   define tb_align8_free(data)                     tb_free((tb_pointer_t)data)  #   define tb_align8_malloc(size)                   tb_malloc(size)  #   define tb_align8_malloc0(size)                  tb_malloc0(size)  #   define tb_align8_nalloc(item, size)             tb_nalloc(item, size)  #   define tb_align8_nalloc0(item, size)            tb_nalloc0(item, size)  #   define tb_align8_ralloc(data, size)             tb_ralloc((tb_pointer_t)data, size)  #else  #   define tb_align8_free(data)                     tb_align_free((tb_pointer_t)data)  #   define tb_align8_malloc(size)                   tb_align_malloc(size, 8)  #   define tb_align8_malloc0(size)                  tb_align_malloc0(size, 8)  #   define tb_align8_nalloc(item, size)             tb_align_nalloc(item, size, 8)  #   define tb_align8_nalloc0(item, size)            tb_align_nalloc0(item, size, 8)  #   define tb_align8_ralloc(data, size)             tb_align_ralloc((tb_pointer_t)data, size, 8)  #endif

內存檢測

TBOX的內存分配在調試模式下,可以檢測支持內存泄露和越界,而且還能精確定位到出問題的那塊內存具體分配位置,和函數調用堆棧。

要使用tbox的內存檢測功能,只需要切換到debug模式編譯:

$ xmake f -m debug  $ xmake

內存泄露檢測

!> 泄露檢測,必須在程序完整退出,確保調用了tb_exit()接口後才能觸發檢測。

內存泄露的檢測必須在程序退出的前一刻,調用tb_exit()的時候,才會執行,如果有泄露,會有詳細輸出到終端上。

    tb_void_t tb_demo_leak()      {          tb_pointer_t data = tb_malloc0(10);      }

輸出:

    [tbox]: [error]: leak: 0x7f9d5b058908 at tb_static_fixed_pool_dump(): 735, memory/impl/static_fixed_pool.c      [tbox]: [error]: data: from: tb_demo_leak(): 43, memory/check.c      [tbox]: [error]:     [0x000001050e742a]: 0   demo.b                              0x00000001050e742a tb_fixed_pool_malloc0_ + 186      [tbox]: [error]:     [0x000001050f972b]: 1   demo.b                              0x00000001050f972b tb_small_pool_malloc0_ + 507      [tbox]: [error]:     [0x000001050f593c]: 2   demo.b                              0x00000001050f593c tb_pool_malloc0_ + 540      [tbox]: [error]:     [0x00000105063cd7]: 3   demo.b                              0x0000000105063cd7 tb_demo_leak + 55      [tbox]: [error]:     [0x00000105063e44]: 4   demo.b                              0x0000000105063e44 tb_demo_memory_check_main + 20      [tbox]: [error]:     [0x0000010505b08e]: 5   demo.b                              0x000000010505b08e main + 878      [tbox]: [error]:     [0x007fff8c95a5fd]: 6   libdyld.dylib                       0x00007fff8c95a5fd start + 1      [tbox]: [error]:     [0x00000000000002]: 7   ???                                 0x0000000000000002 0x0 + 2      [tbox]: [error]: data: 0x7f9d5b058908, size: 10, patch: cc

內存越界檢測

越界溢出的檢測,是實時完成的,而且對libc也做了插樁,所以對常用strcpy,memset等的使用,都會去檢測

    tb_void_t tb_demo_overflow()      {          tb_pointer_t data = tb_malloc0(10);          if (data)          {              tb_memset(data, 0, 11);              tb_free(data);          }      }

輸出:

    [tbox]: [memset]: [overflow]: [0x0 x 11] => [0x7f950b044508, 10]      [tbox]: [memset]: [overflow]: [0x0000010991a1c7]: 0   demo.b                              0x000000010991a1c7 tb_memset + 151      [tbox]: [memset]: [overflow]: [0x000001098a2d01]: 1   demo.b                              0x00000001098a2d01 tb_demo_overflow + 97      [tbox]: [memset]: [overflow]: [0x000001098a3044]: 2   demo.b                              0x00000001098a3044 tb_demo_memory_check_main + 20      [tbox]: [memset]: [overflow]: [0x0000010989a28e]: 3   demo.b                              0x000000010989a28e main + 878      [tbox]: [memset]: [overflow]: [0x007fff8c95a5fd]: 4   libdyld.dylib                       0x00007fff8c95a5fd start + 1      [tbox]: [memset]: [overflow]: [0x00000000000002]: 5   ???                                 0x0000000000000002 0x0 + 2      [tbox]:     [malloc]: [from]: data: from: tb_demo_overflow(): 12, memory/check.c      [tbox]:     [malloc]: [from]:     [0x0000010992662a]: 0   demo.b                              0x000000010992662a tb_fixed_pool_malloc0_ + 186      [tbox]:     [malloc]: [from]:     [0x0000010993892b]: 1   demo.b                              0x000000010993892b tb_small_pool_malloc0_ + 507      [tbox]:     [malloc]: [from]:     [0x00000109934b3c]: 2   demo.b                              0x0000000109934b3c tb_pool_malloc0_ + 540      [tbox]:     [malloc]: [from]:     [0x000001098a2cd7]: 3   demo.b                              0x00000001098a2cd7 tb_demo_overflow + 55      [tbox]:     [malloc]: [from]:     [0x000001098a3044]: 4   demo.b                              0x00000001098a3044 tb_demo_memory_check_main + 20      [tbox]:     [malloc]: [from]:     [0x0000010989a28e]: 5   demo.b                              0x000000010989a28e main + 878      [tbox]:     [malloc]: [from]:     [0x007fff8c95a5fd]: 6   libdyld.dylib                       0x00007fff8c95a5fd start + 1      [tbox]:     [malloc]: [from]:     [0x00000000000002]: 7   ???                                 0x0000000000000002 0x0 + 2      [tbox]:     [malloc]: [from]: data: 0x7f950b044508, size: 10, patch: cc      [tbox]:     [malloc]: [from]: data: first 10-bytes:      [tbox]: ===================================================================================================================================================      [tbox]: 00000000   00 00 00 00  00 00 00 00  00 00                                                                         ..........      [tbox]: [error]: abort at tb_memset(): 255, libc/string/memset.c

內存重疊覆蓋檢測

如果兩塊內存的copy發生了重疊,有可能會覆蓋掉部分數據,導致bug,因此TBOX對此也做了些檢測。

    tb_void_t tb_demo_overlap()      {          tb_pointer_t data = tb_malloc(10);          if (data)          {              tb_memcpy(data, (tb_byte_t const*)data + 1, 5);              tb_free(data);          }      }

輸出

    [tbox]: [memcpy]: [overlap]: [0x7fe9b5042509, 5] => [0x7fe9b5042508, 5]      [tbox]: [memcpy]: [overlap]: [0x000001094403b8]: 0   demo.b                              0x00000001094403b8 tb_memcpy + 632      [tbox]: [memcpy]: [overlap]: [0x000001093c99f9]: 1   demo.b                              0x00000001093c99f9 tb_demo_overlap + 105      [tbox]: [memcpy]: [overlap]: [0x000001093c9a44]: 2   demo.b                              0x00000001093c9a44 tb_demo_memory_check_main + 20      [tbox]: [memcpy]: [overlap]: [0x000001093c0c8e]: 3   demo.b                              0x00000001093c0c8e main + 878      [tbox]: [memcpy]: [overlap]: [0x007fff8c95a5fd]: 4   libdyld.dylib                       0x00007fff8c95a5fd start + 1      [tbox]: [memcpy]: [overlap]: [0x00000000000002]: 5   ???                                 0x0000000000000002 0x0 + 2      [tbox]:     [malloc]: [from]: data: from: tb_demo_overlap(): 58, memory/check.c      [tbox]:     [malloc]: [from]:     [0x0000010945eadb]: 0   demo.b                              0x000000010945eadb tb_small_pool_malloc_ + 507      [tbox]:     [malloc]: [from]:     [0x0000010945b23c]: 1   demo.b                              0x000000010945b23c tb_pool_malloc_ + 540      [tbox]:     [malloc]: [from]:     [0x000001093c99c7]: 2   demo.b                              0x00000001093c99c7 tb_demo_overlap + 55      [tbox]:     [malloc]: [from]:     [0x000001093c9a44]: 3   demo.b                              0x00000001093c9a44 tb_demo_memory_check_main + 20      [tbox]:     [malloc]: [from]:     [0x000001093c0c8e]: 4   demo.b                              0x00000001093c0c8e main + 878      [tbox]:     [malloc]: [from]:     [0x007fff8c95a5fd]: 5   libdyld.dylib                       0x00007fff8c95a5fd start + 1      [tbox]:     [malloc]: [from]:     [0x00000000000002]: 6   ???                                 0x0000000000000002 0x0 + 2      [tbox]:     [malloc]: [from]: data: 0x7fe9b5042508, size: 10, patch: cc      [tbox]:     [malloc]: [from]: data: first 10-bytes:      [tbox]: ===================================================================================================================================================      [tbox]: 00000000   CC CC CC CC  CC CC CC CC  CC CC                                                                         ..........      [tbox]: [error]: abort at tb_memcpy(): 125, libc/string/memcpy.c

內存雙重釋放檢測

    tb_void_t tb_demo_free2()      {          tb_pointer_t data = tb_malloc0(10);          if (data)          {              tb_free(data);              tb_free(data);          }      }

輸出

    [tbox]: [assert]: expr[((impl->used_info)[(index) >> 3] & (0x1 << ((index) & 7)))]: double free data: 0x7fd93386c708 at tb_static_fixed_pool_free(): 612, memory/impl/static_fixed_pool.c      [tbox]:     [0x0000010c9f553c]: 0   demo.b                              0x000000010c9f553c tb_static_fixed_pool_free + 972      [tbox]:     [0x0000010c9ee7a9]: 1   demo.b                              0x000000010c9ee7a9 tb_fixed_pool_free_ + 713      [tbox]:     [0x0000010ca01ff5]: 2   demo.b                              0x000000010ca01ff5 tb_small_pool_free_ + 885      [tbox]:     [0x0000010c9fdb4f]: 3   demo.b                              0x000000010c9fdb4f tb_pool_free_ + 751      [tbox]:     [0x0000010c96ac8e]: 4   demo.b                              0x000000010c96ac8e tb_demo_free2 + 158      [tbox]:     [0x0000010c96ae44]: 5   demo.b                              0x000000010c96ae44 tb_demo_memory_check_main + 20      [tbox]:     [0x0000010c96208e]: 6   demo.b                              0x000000010c96208e main + 878      [tbox]:     [0x007fff8c95a5fd]: 7   libdyld.dylib                       0x00007fff8c95a5fd start + 1      [tbox]:     [0x00000000000002]: 8   ???                                 0x0000000000000002 0x0 + 2      [tbox]: [error]: free(0x7fd93386c708) failed! at tb_demo_free2(): 37, memory/check.c at tb_static_fixed_pool_free(): 649, memory/impl/static_fixed_pool.c      [tbox]: [error]: data: from: tb_demo_free2(): 33, memory/check.c      [tbox]: [error]:     [0x0000010c9ee42a]: 0   demo.b                              0x000000010c9ee42a tb_fixed_pool_malloc0_ + 186      [tbox]: [error]:     [0x0000010ca0072b]: 1   demo.b                              0x000000010ca0072b tb_small_pool_malloc0_ + 507      [tbox]: [error]:     [0x0000010c9fc93c]: 2   demo.b                              0x000000010c9fc93c tb_pool_malloc0_ + 540      [tbox]: [error]:     [0x0000010c96ac27]: 3   demo.b                              0x000000010c96ac27 tb_demo_free2 + 55      [tbox]: [error]:     [0x0000010c96ae44]: 4   demo.b                              0x000000010c96ae44 tb_demo_memory_check_main + 20      [tbox]: [error]:     [0x0000010c96208e]: 5   demo.b                              0x000000010c96208e main + 878      [tbox]: [error]:     [0x007fff8c95a5fd]: 6   libdyld.dylib                       0x00007fff8c95a5fd start + 1      [tbox]: [error]:     [0x00000000000002]: 7   ???                                 0x0000000000000002 0x0 + 2      [tbox]: [error]: data: 0x7fd93386c708, size: 10, patch: cc      [tbox]: [error]: data: first 10-bytes:      [tbox]: ===================================================================================================================================================      [tbox]: 00000000   00 00 00 00  00 00 00 00  00 00                                                                         ..........      [tbox]: [error]: abort at tb_static_fixed_pool_free(): 655, memory/impl/static_fixed_pool.c