C 的全包緩衝區
自助餐是一個有 4 種模式的標記聯合體。
// Hard values show 64-bit
union Buffet {
struct ptr {
char * data
size_t len
size_t off : 62 , tag : 2 // tag = OWN|SSV|VUE
}
struct sso {
char data [ 22 ]
uint8_t refcnt
uint8_t len : 6 , tag : 2 // tag = SSO
}
}
sizeof ( Buffet ) == 24
此標籤設定自助餐的模式:
OWN
共同擁有一家商店的一部分SSO
嵌入字元數組SSV
(小字串視圖)視圖VUE
對任何資料的非擁有視圖如果是 OWN, Buffet.data指向分配的堆疊儲存:
struct Store {
size_t cap // store capacity
size_t len // store length
uint32_t refcnt // number of views on store
uint32_t canary // invalidates store if modified
char data [] // buffer data, shared by owning views
}
#include "../buffet.h"
int main () {
// SHARED OWN =================
char large [] = "DATA STORE IS HEAP ALLOCATION." ;
Buffet own1 = bft_memcopy ( large , sizeof ( large ) - 1 );
// Now own1 owns a store housing a copy of `large`
bft_dbg ( & own1 );
//-> OWN 30 "DATA STORE ..."
// View "STORE" in own1 :
Buffet own2 = bft_view ( & own1 , 5 , 5 );
// Now own1 and own2 share the store, whose refcount is 2
bft_dbg ( & own2 );
//-> OWN 5 "STORE"
// SSO & SSV =================
char small [] = "SMALL STRING" ;
Buffet sso1 = bft_memcopy ( small , sizeof ( small ) - 1 );
bft_dbg ( & sso1 );
//-> SSO 12 "SMALL STRING"
// View "STRING" in sso1 :
Buffet ssv1 = bft_view ( & sso1 , 6 , 6 );
bft_dbg ( & ssv1 );
//-> SSV 6 "STRING"
// VUE =======================
char any [] = "SOME BYTES" ;
// View "BYTES" in `any` :
Buffet vue1 = bft_memview ( any + 5 , 5 );
bft_dbg ( & vue1 );
//-> VUE 5 "BYTES"
return 0 ;
}
make && make check
雖然單元測試很廣泛,但可能尚未涵蓋所有情況。
自助餐旨在防止記憶體錯誤,包括來自用戶的記憶體錯誤。
(當然,失去範圍等除外。)
// (pseudo code)
// overflow
buf = new ( 8 )
append ( buf , large_str ) // Done
// invalid ref
buf = memcopy ( short_str ) // SSO
view = view ( buf )
append ( buf , large_str ) // would mutate SSO to OWN
// => abort & warn "Append would invalidate views on SSO"
// double-free
bft_free ( buf )
bft_free ( buf ) // OK
// use-after-free
bft_free ( buf )
append ( buf , "foo" ) // Done. Now buf is "foo".
// aliasing
alias = buf // should be `alias = bft_dup(buf)`
bft_free ( buf )
bft_free ( alias ) // OK. Possible warning "Bad canary. Double free ?"
// Etc...
為此,像view()或free()這樣的操作可以檢查儲存的標頭。
如果錯誤,操作將中止並返回空自助餐。
透過#define MEMCHECK
或使用以下命令來啟用檢查
MEMCHECK=1 make
警告透過#define DEBUG
或使用以下命令來啟用
DEBUG=1 make
注意:即使進行了檢查,某些別名也可能是致命的。
own = memcopy ( large_str )
view = view ( own )
alias = view
bft_free ( view )
bft_free ( own ) // refcnt == 0, free(store) !
// alias now points into freed memory...
請參閱src/check.c單元測試和警告輸出。
make && make bench
(需要libbenchmark-dev )
注意:庫沒有太多優化,工作台可能很業餘。
在較弱的 Core i3 上:
MEMVIEW_cpp/8 0.609 奈秒 MEMVIEW_buffet/8 6.36 奈秒 MEMCOPY_c/8 16.7 奈秒 MEMCOPY_buffet/8 11.9 奈秒 MEMCOPY_c/32 15.3 奈秒 MEMCOPY_buffet/32 26.3 奈秒 MEMCOPY_c/128 16.8 奈秒 MEMCOPY_buffet/128 29.8 奈秒 MEMCOPY_c/512 24.9 奈秒 MEMCOPY_buffet/512 39.3 奈秒 MEMCOPY_c/2048 94.1 奈秒 MEMCOPY_buffet/2048 109 奈秒 MEMCOPY_c/8192 196 奈秒 MEMCOPY_buffet/8192 282 奈秒 APPEND_cpp/8/4 10.9 奈秒 APPEND_buffet/8/4 16.3 奈秒 APPEND_cpp/8/16 36.5 奈秒 APPEND_buffet/8/16 30.2 奈秒 APPEND_cpp/24/4 49.0 奈秒 APPEND_buffet/24/4 30.1 奈秒 APPEND_cpp/24/32 48.1 奈秒 APPEND_buffet/24/32 28.8 奈秒 SPLITJOIN_c 2782 奈秒 SPLITJOIN_cpp 3317 奈秒 SPLITJOIN_buffet 1397 奈秒
bft_new
bft_memcopy
bft_memview
bft_copy
bft_copyall
bft_視圖
bft_dup(不要給自助餐起別名,使用這個)
bft_追加
bft_split
bft_splitstr
bft_join
bft_free
bft_cmp
bft_cap
bft_len
bft_數據
bft_cstr
bft_導出
bft_print
bft_dbg
Buffet bft_new (size_t cap)
建立一個新的空自助餐的最小容量上限。
Buffet buf = bft_new ( 40 );
bft_dbg ( & buf );
// OWN 0 ""
Buffet bft_memcopy (const char *src, size_t len)
透過從src複製len 個位元組來創建一個新的自助餐。
Buffet copy = bft_memcopy ( "Bonjour" , 3 );
// SSO 3 "Bon"
Buffet bft_memview (const char *src, size_t len)
建立一個新的Buffet,查看src中的len位元組。
您將獲得一個進入src 的窗口,無需複製或分配。
注意:您不應該直接記憶體查看自助餐的數據。使用視圖()
char src [] = "Eat Buffet!" ;
Buffet view = bft_memview ( src + 4 , 6 );
// VUE 6 "Buffet"
Buffet bft_copy (const Buffet *src, ptrdiff_t off, size_t len)
將距離Buffet src偏移的len 個位元組複製到新的Buffet 中。
Buffet src = bft_memcopy ( "Bonjour" , 7 );
Buffet cpy = bft_copy ( & src , 3 , 4 );
// SSO 4 "jour"
Buffet bft_copyall (const Buffet *src)
將Buffet src中的所有位元組複製到新的Buffet 。
Buffet bft_view (Buffet *src, ptrdiff_t off, size_t len)
查看Buffet src的len字節,從off開始。
您將獲得一個進入src 的窗口,無需複製或分配。
傳回的內部類型取決於src類型:
view(SSO) -> SSV
(重新計數)view(SSV) -> SSV
src目標上的 SSVview(OWN) -> OWN
(作為引用計數的商店共同所有者)view(VUE) -> VUE
src目標上的 VUE如果傳回的是 OWN,則目標儲存在此之前都不會被釋放
#include "../buffet.h"
int main () {
char text [] = "Bonjour monsieur Buddy. Already speaks french!" ;
// view sso
Buffet sso = bft_memcopy ( text , 16 ); // "Bonjour monsieur"
Buffet ssv = bft_view ( & sso , 0 , 7 );
bft_dbg ( & ssv );
// view ssv
Buffet Bon = bft_view ( & ssv , 0 , 3 );
bft_dbg ( & Bon );
// view own
Buffet own = bft_memcopy ( text , sizeof ( text ));
Buffet ownview = bft_view ( & own , 0 , 7 );
bft_dbg ( & ownview );
// detach view
bft_append ( & ownview , "!" , 1 );
// bft_free(&ownview);
bft_free ( & own ); // Done
// view vue
Buffet vue = bft_memview ( text + 8 , 8 ); // "Good"
Buffet mon = bft_view ( & vue , 0 , 3 );
bft_dbg ( & mon );
return 0 ;
}
$ cc view.c libbuffet.a -o view && ./view
SSV 7 data:"Bonjour"
SSV 3 data:"Bon"
OWN 7 data:"Bonjour"
VUE 3 data:"mon"
Buffet bft_dup (const Buffet *src)
建立src的淺表副本。
使用它而不是為自助餐起別名。
Buffet src = bft_memcopy ( "Hello" , 5 );
Buffet cpy = src ; // BAD
Buffet cpy = bft_dup ( & src ); // GOOD
bft_dbg ( & cpy );
// SSO 5 "Hello"
Rem:別名大部分都可以工作,但會擾亂引用計數(如果啟用儲存保護,則不會崩潰):
Buffet alias = sso ; //ok if sso was not viewed
Buffet alias = own ; //not refcounted
Buffet alias = vue ; //ok
void bft_free (Buffet *buf)
丟棄buf 。
安全:
#include "../buffet.h"
int main () {
char text [] = "Le grand orchestre de Patato Valdez" ;
Buffet own = bft_memcopy ( text , sizeof ( text ));
Buffet ref = bft_view ( & own , 9 , 9 ); // "orchestre"
bft_free ( & own ); // A bit soon but ok, --refcnt
bft_dbg ( & own ); // SSO 0 ""
bft_free ( & ref ); // Was last co-owner, store is released
Buffet sso = bft_memcopy ( text , 8 ); // "Le grand"
Buffet ref2 = bft_view ( & sso , 3 , 5 ); // "grand"
bft_free ( & sso ); // WARN line:328 bft_free: SSO has views on it
bft_free ( & ref2 );
bft_free ( & sso ); // OK now
bft_dbg ( & sso ); // SSO 0 ""
return 0 ;
}
$ valgrind --leak-check=full ./bin/ex/free
All heap blocks were freed -- no leaks are possible
size_t bft_cat (Buffet *dst, const Buffet *buf, const char *src, size_t len)
將src的buf和len位元組連接到結果dst 。
傳回總長度或錯誤時傳回 0。
Buffet buf = bft_memcopy ( "abc" , 3 );
Buffet dst ;
size_t totlen = bft_cat ( & dst , & buf , "def" , 3 );
bft_dbg ( & dst );
// SSO 6 "abcdef"
size_t bft_append (Buffet *dst, const char *src, size_t len)
將len 個位元組從src追加到dst 。
返回新長度或出錯時返回 0。
Buffet buf = bft_memcopy ( "abc" , 3 );
size_t newlen = bft_append ( & buf , "def" , 3 );
bft_dbg ( & buf );
// SSO 6 "abcdef"
注意:如果buf有視圖,並且會從 SSO 突變為 OWN 以增加容量,則傳回失敗,使視圖無效:
Buffet foo = bft_memcopy ( "short foo " , 10 );
Buffet view = bft_view ( & foo , 0 , 5 );
// would mutate to OWN :
size_t rc = bft_append ( & foo , "now too long for SSO" );
assert ( rc == 0 ); // meaning aborted
為了防止這種情況,請在添加到小型自助餐之前釋放視圖。
Buffet* bft_split (const char* src, size_t srclen, const char* sep, size_t seplen,
int *outcnt)
將src沿著分隔符號sep拆分為長度為*outcnt
的自助餐 Vue 清單。
由於由視圖組成,您可以free(list)
而不會洩漏,前提是沒有元素通過附加到它而成為所有者。
Buffet* bft_splitstr (const char *src, const char *sep, int *outcnt);
內部使用strlen進行方便的分割。
int cnt ;
Buffet * parts = bft_splitstr ( "Split me" , " " , & cnt );
for ( int i = 0 ; i < cnt ; ++ i )
bft_print ( & parts [ i ]);
// VUE 5 "Split"
// VUE 2 "me"
free ( parts );
Buffet bft_join (Buffet *list, int cnt, const char* sep, size_t seplen);
將分隔符號sep上的清單加入新的自助餐。
int cnt ;
Buffet * parts = bft_splitstr ( "Split me" , " " , & cnt );
Buffet back = bft_join ( parts , cnt , " " , 1 );
bft_dbg ( & back );
// SSO 8 'Split me'
int bft_cmp (const Buffet *a, const Buffet *b)
使用memcmp
比較兩個自助餐的數據。
size_t bft_cap (Buffet *buf)
取得目前容量。
size_t bft_len (Buffet *buf)`
取得當前長度。
const char* bft_data (const Buffet *buf)`
取得當前資料指針。
若要確保buf.len
處的空終止,請使用bft_cstr 。
const char* bft_cstr (const Buffet *buf, bool *mustfree)
取得最大長度buf.len
的以 null 結尾的 C 字串形式的目前資料。
如果需要(當buf是視圖時),資料將複製到新的 C 字串中,如果設定了Mustfree ,則必須釋放該字串。
char* bft_export (const Buffet *buf)
將buf.len
之前的資料複製到必須釋放的新 C 字串中。
void bft_print (const Buffet *buf)`
列印直到buf.len
資料。
void bft_dbg (Buffet *buf)
列印buf狀態。
Buffet buf ;
bft_memcopy ( & buf , "foo" , 3 );
bft_dbg ( & buf );
// SSO 3 "foo"