題外話
以前也用C寫過字符串,主要應用的領域是,大字符串,文件讀取方面.寫的很粗暴,用的湊合著.那時候看見雲風前輩的一個開源的 cstring 串.
當時簡單觀摩了一下,覺得挺好的.也沒細看.過了較長一段時間,想整合一下,將大字符串和雲風的cstring 短簡單的串合在一起變成一種.但是自己
認真復制了一遍後發現.
1.整合不了 雲風(後面都省略前輩二字,覺得雲風兩個字,就已經帥的不行了)簡單cstring.因為處理的領域不一樣.
雲風的 cstring => String , 而自己寫的操作文件的c簡單串 => StringBuilder.
2.技巧太多了,不明覺厲,但是雲風用的技巧,都會解釋,畢竟都是C開發中常用的技巧.
3.自己很菜,只能是瞎子摸象,看見只是部分,更多的需要大家自己參悟.
參考資料
1.雲風博客簡單字符串簡介 http://blog.codingnow.com/2013/09/cstring.html
2.雲風githup cstring https://github.com/cloudwu/cstring
3.gcc inline解釋 在線文檔 https://gcc.gnu.org/onlinedocs/gcc-5.3.0/gcc/Inline.html#Inline
4.字符串hash 函數簡介 http://www.cnblogs.com/uvsjoh/archive/2012/03/27/2420120.html#3240817
5. 簡單實現原子操作 http://www.cnblogs.com/life2refuel/p/5024289.html#3326123
6. assert 使用 http://www.cnblogs.com/ggzss/archive/2011/08/18/2145017.html
7. 位運算 http://blog.sina.com.cn/s/blog_7b7cad23010163vy.html
8.stdarg.h c可變參數詳解 http://www.cnblogs.com/life2refuel/p/4984275.html
前言
這次直接切入正題,也許你會不屑一顧,還是想 分享一個故事
面朝大海,春暖花開 海子 於 1989 從明天起,做一個幸福的人 喂馬、劈柴,周游世界 從明天起,關心糧食和蔬菜 我有一所房子,面朝大海,春暖花開 從明天起,和每一個親人通信 告訴他們我的幸福 那幸福的閃電告訴我的 我將告訴每一個人 給每一條河每一座山取一個溫暖的名字 陌生人,我也為你祝福 願你有一個燦爛的前程 願你有情人終成眷屬 願你在塵世獲得幸福 我只願面朝大海,春暖花開
同名歌曲
面朝大海 http://music.163.com/#/song?id=27946316
正題
首先下載雲風的cstring 源碼 結構如下:
首先 看 cstring.h 文件
#ifndef cstring_h #define cstring_h #include <stdint.h> #include <stddef.h> #define CSTRING_PERMANENT 1 #define CSTRING_INTERNING 2 #define CSTRING_ONSTACK 4 #define CSTRING_INTERNING_SIZE 32 #define CSTRING_STACK_SIZE 128 struct cstring_data { char * cstr; uint32_t hash_size; uint16_t type; uint16_t ref; }; typedef struct _cstring_buffer { struct cstring_data * str; } cstring_buffer[1]; typedef struct cstring_data * cstring; #define CSTRING_BUFFER(var) \ char var##_cstring [CSTRING_STACK_SIZE] = { '\0' }; \ struct cstring_data var##_cstring_data = { var##_cstring , 0, CSTRING_ONSTACK, 0 }; \ cstring_buffer var; \ var->str = &var##_cstring_data; #define CSTRING_LITERAL(var, cstr) \ static cstring var = NULL; \ if (var) {} else { \ cstring tmp = cstring_persist(""cstr, (sizeof(cstr)/sizeof(char))-1); \ if (!__sync_bool_compare_and_swap(&var, NULL, tmp)) { \ cstring_free_persist(tmp); \ } \ } #define CSTRING(s) ((s)->str) #define CSTRING_CLOSE(var) \ if ((var)->str->type != 0) {} else \ cstring_release((var)->str); /* low level api, don't use directly */ cstring cstring_persist(const char * cstr, size_t sz); void cstring_free_persist(cstring s); /* public api */ cstring cstring_grab(cstring s); void cstring_release(cstring s); cstring cstring_cat(cstring_buffer sb, const char * str); cstring cstring_printf(cstring_buffer sb, const char * format, ...) #ifdef __GNUC__ __attribute__((format(printf, 2, 3))) #endif ; int cstring_equal(cstring a, cstring b); uint32_t cstring_hash(cstring s); #endif
第一部分 對頭文件 cstring.h 簡單解析如下(對於源碼全部解釋,自己說的不爽,別人看也會不爽,畢竟不是原創)
1.1 字符串類型
#define CSTRING_PERMANENT 1
上面聲明表示一個 永久的串 類型,實現采用static 變量類型聲明
#define CSTRINT_INTERNING 2
上面表示一個 符號表字符串 類型,實現方式 直接 "root" 這麼搞
#define CSTRING_ONSTACK 4
這大家都知道 臨時字符串 類型,實現方式 利用宏嵌到 函數代碼體中
1.2 字符串大小宏
#define CSTRING_INTERNING_SIZE 32
上面是符號表串,小於32字節的 都可以聲明為 CSTRINT_INTERNING
#define CSTRING_STACK_SIZE 128
上面是表明小於128字節的都可以放到棧上 類型 CSTRING_ONSTACK
2.1 特殊的結構體
typedef struct cstring_buffer { struct cstring_data * str; } cstring_buffer[1];
上面 定義的 cstring_buffer 類型,是C中一個聲明技巧
例如 cstring_buffer cb; 其中 cb 內存分配在 棧上 但是可以當指針使用,傳入到 struct cstring_buffer* 地方.
更簡單一點如下,其它就自己悟吧
cstring_buffer cb; => struct cstring_buffer cb[1];
3.1 函數宏分析
#define CSTRING_BUFFER(var) \ char var##_cstring[CSTRING_STACK_SIZE] = { '\0' }; \ struct cstring_data var##_cstring_data = { var##_cstring, 0, CSTRING_ONSTACK, 0 }; \ cstring_buffer var; \ var->str = &var##cstring_data;
這個宏 也很巧妙, ## 表示鏈接宏 假如 var 是 abc,var只能同變量,不能有雙引號
那麼就聲明了一個
char abc_cstring[128] = { '\0' };
這個變量內存在棧上,通常不需要回收.
這個宏作用是 聲明了一個名為 var 的 cstring_buffer 對象 但是在函數結束時,應該使用 CSTRING_CLOSE(var) 關閉它。
3.2 另一個出彩的函數宏
#define CSTRING_LITERAL(var, cstr) \ static cstring var = NULL; \ if (var) {} else { \ cstring tmp = cstring_persist(""cstr, (sizeof(cstr)/sizeof(char)-1)); \ if(!__sync_bool_compare_and_swap(&var, NULL, tmp)){ \ cstring_free_persist(tmp); \ } \ }
這個函數宏聲明變量都是 全局存儲區的變量,這裡認為是常量cstring.
但是cstr必須是 引起""引起宏.
這裡這個
cstring_persist(""cstr, (sizeof(cstr)/sizeof(char)-1));
""特別亮,在函數編譯的時候就能找出錯誤!第二參數 常量字符串最後一個字符的索引
後面 __sync_bool_compare_and_swap 是gcc 內置的原子函數,推薦用最新的gcc版本測試
詳細一點介紹是
bool __sync_bool_compare_and_swap (type *ptr, type oldval, type newval, ...);
__sync_bool_compare_and_swap 內置函數比較 oldval 和 *ptr。 如果它們匹配,就把 newval 復制到 *ptr。 此時返回返回值是 true,否則是 false.
這個函數 在並發編程中甩掉互斥鎖N條街.
4.1 函數解析,首先是cstring 如果需要把字符串做參數傳遞,就應該使用 cstring 類型,而不是 cstring_buffer 類型。
CSTRING(var) 可以把 var 這個 cstring_buffer 對象,轉換為 cstring 類型。
但是,在對 cstring_buffer 對象做新的操作後,這個 cstring 可能無效。
所以每次傳遞 cstring_buffer 內的值,最好都重新用 CSTRING 宏取一次。
函數調用的參數以及返回值,都應該使用 cstring 類型。
如果 cstring 是由外部傳入的,無法確定它的數據在棧上還是堆上,所以不能長期持有。
如果需要把 cstring 保存在數據結構中,可以使用這對 API :
cstring cstring_grab(cstring s); void cstring_release(cstring s);
把 cstring 轉化為標准的 const char * ,只需要用 s->cstr 即可。
cstring 的比較操作以及 hash 操作都比 const char * 廉價,所以,請使用以下 API :
int cstring_equal(cstring a, cstring b); uint32_t cstring_hash(cstring s);
這裡還有一個函數聲明
cstring cstring_printf(cstring_buffer sb, const char * format, ...) #ifdef __GNUC__ __attribute__((format(printf, 2, 3))) #endif
重點 是 後面的 __attribute__() 這也是個gcc 內置語法,是對編譯器編譯行為進行一些約定 , 這裡 format (printf, 2, 3)告訴編譯器,
cstring_printf的format相當於printf的format, 而可變參數是從cstring_printf的第3個參數開始。
這樣編譯器就會在編譯時用和printf一樣的check法則來確認可變參數是否正確了.
關於 gcc編譯器的控制行為,還是比較多的.自己可以搜索一下 gcc __attribute__,這些都很死,不是大神推薦不要用太多編譯器指令,不通用技巧性太強,容易東施效颦!
到這裡第一部分 基本就解釋 完畢了.
這裡再擴展一點 對於結構
struct cstring_data { char * cstr; uint32_t hash_size; uint16_t type; uint16_t ref; };
後面使用的時候 常出現這樣的代碼
struct cstring_data *p = malloc(sizeof(struct cstring_data) + sz + 1); // todo: memory alloc error assert(p); void * ptr = (void *)(p + 1); p->cstr = ptr; p->type = 0; p->ref = 1; memcpy(ptr, cstr, sz); ((char*)ptr)[sz] = '\0'; p->hash_size = 0;
這裡的一個技巧是 直接一次 malloc 將兩個 內存都分配好了 .
第一個 sizeof (struct cstring_data) 是給 p用的,
第二個 sizeof (struct cstring_data) + sz + 1 給 p->cstr 用的,.
對於這個技巧還用更 巧的是
struct cstring_data { uint32_t hash_size; uint16_t type; uint16_t ref; char [] cstr; };
這種結構 聲明方式和方式一樣
struct cstring_data *p = malloc(sizeof(struct cstring_data) + sz + 1);
下面這種方式和上面比有點 內存更小了, 小了 sizeof (cstr).
不要在C中問出,少了4字節有什麼意義,那只能推薦你去學java吧.
但是 為什麼 雲風沒有這麼干呢. 是這樣的 後面 那種聲明方式為不完全類型, 有點 像
void* arg;
內存只能通過堆分配,不在在棧上分配,而 cstring 需要運用棧內存,具體看下面宏.
#define CSTRING_BUFFER(var) \ char var##_cstring[CSTRING_STACK_SIZE] = { '\0' }; \ struct cstring_data var##_cstring_data = { var##_cstring, 0, CSTRING_ONSTACK, 0 }; \ cstring_buffer var; \ var->str = &var##_cstring_data;
這裡 再擴展一下,吐槽一下 雲風前輩
struct cstring_data *p = malloc(sizeof(struct cstring_data) + sz + 1); // todo: memory alloc error assert(p);
第一次見這樣代碼,看一遍覺得好,屌.
看第二遍 有點不對吧.
看第三遍 確定 這樣是 用錯了assert, assert 在 開啟 NDEBUG 會失效.
假如 程序正式跑了,設置了
gcc -Wall -INDEBUG -o $^ $@
上面代碼 assert 就等同於
#ifdef NDEBUG #define assert(expression) ((void)0) #endif
那麼程序 假如 另一個 BUG,將 內存吃完了,這裡 就是 未定義 修改 未知內存,基本是返回NULL,操作NULL,程序崩了.
服務器當了,查原因 還沒日志...... 這是 不好的, 反正是 他用錯了
還是用下面這樣質樸的代碼吧
struct cstring_data *p = malloc(sizeof(struct cstring_data) + sz + 1); // todo: memory alloc error if(NULL == p) { fprintf(stderr, "[%s][%d][%s][error:malloc struct cstring_data return NULL!]",__FILE__, __LINE__, __func__) ; return NULL; }
上面寫的比較簡單,還需要錯誤輸出需要考慮時間.
第二部分 寫一個簡單例子 直接用雲風 的 test.c
#include "cstring.h" #include <stdio.h> static cstring foo(cstring t) { CSTRING_LITERAL(hello, "hello"); CSTRING_BUFFER(ret); if (cstring_equal(hello,t)) { cstring_cat(ret, "equal"); } else { cstring_cat(ret, "not equal"); } return cstring_grab(CSTRING(ret)); } static void test() { CSTRING_BUFFER(a); cstring_printf(a, "%s", "hello"); cstring b = foo(CSTRING(a)); printf("%s\n", b->cstr); cstring_printf(a, "very long string %01024d",0); printf("%s\n", CSTRING(a)->cstr); CSTRING_CLOSE(a); cstring_release(b); } int main() { test(); return 0; }
我們采用 Ubuntu 測試一下
編譯失敗,按照下面改
vim Makefile gcc -g -Wall -march=native -o test test.c cstring.c Esc wq!
最後結果如下
到這裡 我們 代碼 已經跑起來了. 對於 test.c中
我們簡單 解釋一下 其中 test.c使用到的 api
CSTRING_BUFFER(a); cstring_printf(a, "%s", "hello");
一開a字符串在棧上,後面輸出的串比較小,仍然在棧上.後面有個
CSTRING_CLOSE(a);
關閉這個內存,本質是
#define CSTRING_CLOSE(var) \ if ((var)->str->type != 0) {} else \ cstring_release((var)->str);
因為 a->str->type == CSTRING_ONSTACK != 0 所以 cstring_release執行後沒有反應,可有可無.
但是 推薦 CSTRING_BUFFER 和 CSTRING_CLOSE 成對出現.
還有就是 foo 函數裡面
CSTRING_LITERAL(hello, "hello"); CSTRING_BUFFER(ret);
hello 相當於 符號表中字符串,生存周期是 和程序同生共死的.ret 目前在棧上.
後面
if (cstring_equal(hello,t)) { cstring_cat(ret, "equal"); } else { cstring_cat(ret, "not equal"); }
這個二者 執行 的 cstring_cat(ret, "equal"); 結果是 塞得字符串小 ret仍然是棧上的.
後面返回
return cstring_grab(CSTRING(ret));
變成運行時串 既
cs->type = CSTRING_INTERNING;
cs->ref = 0;
所以最後 就不需要 CSTRING_CLOSE (ret);
到了
cstring_printf(a, "very long string %01024d",0);
變成待釋放的臨時串 r->type = 0; r->ref = 1;
技巧很多,主要還是需要看 源碼, 將在第三部剖析一下 實現 string.c中的一些技巧!
第三部分 string.c 源碼 觀察
#include "cstring.h" #include <stdio.h> #include <stdlib.h> #include <assert.h> #include <string.h> #include <stdarg.h> #define FORMAT_TEMP_SIZE 1024 #define INTERNING_POOL_SIZE 1024 // HASH_START_SIZE must be 2 pow #define HASH_START_SIZE 16 struct string_node { struct cstring_data str; char buffer[CSTRING_INTERNING_SIZE]; struct string_node * next; }; struct string_pool { struct string_node node[INTERNING_POOL_SIZE]; }; struct string_interning { int lock; int size; struct string_node ** hash; struct string_pool * pool; int index; int total; }; static struct string_interning S; static inline void LOCK() { while (__sync_lock_test_and_set(&(S.lock),1)) {} } static inline void UNLOCK() { __sync_lock_release(&(S.lock)); } static void insert_node(struct string_node ** hash, int sz, struct string_node *n) { uint32_t h = n->str.hash_size; int index = h & (sz-1); n->next = hash[index]; hash[index] = n; } static void expand(struct string_interning * si) { int new_size = si->size * 2; if (new_size < HASH_START_SIZE) { new_size = HASH_START_SIZE; } assert(new_size > si->total); struct string_node ** new_hash = malloc(sizeof(struct string_node *) * new_size); memset(new_hash, 0, sizeof(struct string_node *) * new_size); int i; for (i=0;i<si->size;i++) { struct string_node *node = si->hash[i]; while (node) { struct string_node * tmp = node->next; insert_node(new_hash, new_size, node); node = tmp; } } free(si->hash); si->hash = new_hash; si->size = new_size; } static cstring interning(struct string_interning * si, const char * cstr, size_t sz, uint32_t hash) { if (si->hash == NULL) { return NULL; } int index = (int)(hash & (si->size-1)); struct string_node * n = si->hash[index]; while(n) { if (n->str.hash_size == hash) { if (strcmp(n->str.cstr, cstr) == 0) { return &n->str; } } n = n->next; } // 80% (4/5) threshold if (si->total * 5 >= si->size * 4) { return NULL; } if (si->pool == NULL) { // need not free pool // todo: check memory alloc error si->pool = malloc(sizeof(struct string_pool)); assert(si->pool); si->index = 0; } n = &si->pool->node[si->index++]; memcpy(n->buffer, cstr, sz); n->buffer[sz] = '\0'; cstring cs = &n->str; cs->cstr = n->buffer; cs->hash_size = hash; cs->type = CSTRING_INTERNING; cs->ref = 0; n->next = si->hash[index]; si->hash[index] = n; return cs; } static cstring cstring_interning(const char * cstr, size_t sz, uint32_t hash) { cstring ret; LOCK(); ret = interning(&S, cstr, sz, hash); if (ret == NULL) { expand(&S); ret = interning(&S, cstr, sz, hash); } ++S.total; UNLOCK(); assert(ret); return ret; } static uint32_t hash_blob(const char * buffer, size_t len) { const uint8_t * ptr = (const uint8_t *) buffer; size_t h = len; size_t step = (len>>5)+1; size_t i; for (i=len; i>=step; i-=step) h = h ^ ((h<<5)+(h>>2)+ptr[i-1]); if (h == 0) return 1; else return h; } void cstring_free_persist(cstring s) { if (s->type == CSTRING_PERMANENT) { free(s); } } static cstring cstring_clone(const char * cstr, size_t sz) { if (sz < CSTRING_INTERNING_SIZE) { return cstring_interning(cstr, sz, hash_blob(cstr,sz)); } struct cstring_data * p = malloc(sizeof(struct cstring_data) + sz + 1); // todo: memory alloc error assert(p); void * ptr = (void *)(p + 1); p->cstr = ptr; p->type = 0; p->ref = 1; memcpy(ptr, cstr, sz); ((char *)ptr)[sz] = '\0'; p->hash_size = 0; return p; } cstring cstring_persist(const char * cstr, size_t sz) { cstring s = cstring_clone(cstr, sz); if (s->type == 0) { s->type = CSTRING_PERMANENT; s->ref = 0; } return s; } cstring cstring_grab(cstring s) { if (s->type & (CSTRING_PERMANENT | CSTRING_INTERNING)) { return s; } if (s->type == CSTRING_ONSTACK) { cstring tmp = cstring_clone(s->cstr, s->hash_size); return tmp; } else { if (s->ref == 0) { s->type = CSTRING_PERMANENT; } else { __sync_add_and_fetch(&s->ref,1); } return s; } } void cstring_release(cstring s) { if (s->type != 0) { return; } if (s->ref == 0) { return; } if (__sync_sub_and_fetch(&s->ref,1) == 0) { free(s); } } uint32_t cstring_hash(cstring s) { if (s->type == CSTRING_ONSTACK) return hash_blob(s->cstr, s->hash_size); if (s->hash_size == 0) { s->hash_size = hash_blob(s->cstr, strlen(s->cstr)); } return s->hash_size; } int cstring_equal(cstring a, cstring b) { if (a == b) return 1; if ((a->type == CSTRING_INTERNING) && (b->type == CSTRING_INTERNING)) { return 0; } if ((a->type == CSTRING_ONSTACK) && (b->type == CSTRING_ONSTACK)) { if (a->hash_size != b->hash_size) { return 0; } return memcmp(a->cstr, b->cstr, a->hash_size) == 0; } uint32_t hasha = cstring_hash(a); uint32_t hashb = cstring_hash(b); if (hasha != hashb) { return 0; } return strcmp(a->cstr, b->cstr) == 0; } static cstring cstring_cat2(const char * a, const char * b) { size_t sa = strlen(a); size_t sb = strlen(b); if (sa + sb < CSTRING_INTERNING_SIZE) { char tmp[CSTRING_INTERNING_SIZE]; memcpy(tmp, a, sa); memcpy(tmp+sa, b, sb); tmp[sa+sb] = '\0'; return cstring_interning(tmp, sa+sb, hash_blob(tmp,sa+sb)); } struct cstring_data * p = malloc(sizeof(struct cstring_data) + sa + sb + 1); // todo: memory alloc error assert(p); char * ptr = (char *)(p + 1); p->cstr = ptr; p->type = 0; p->ref = 1; memcpy(ptr, a, sa); memcpy(ptr+sa, b, sb); ptr[sa+sb] = '\0'; p->hash_size = 0; return p; } cstring cstring_cat(cstring_buffer sb, const char * str) { cstring s = sb->str; if (s->type == CSTRING_ONSTACK) { int i = (int)s->hash_size; while (i < CSTRING_STACK_SIZE-1) { s->cstr[i] = *str; if (*str == '\0') { return s; } ++s->hash_size; ++str; ++i; } s->cstr[i] = '\0'; } cstring tmp = s; sb->str = cstring_cat2(tmp->cstr, str); cstring_release(tmp); return sb->str; } static cstring cstring_format(const char * format, va_list ap) { static char * cache = NULL; char * result; char * temp = cache; // read cache buffer atomic if (temp) { temp = __sync_val_compare_and_swap(&cache, temp, NULL); } if (temp == NULL) { temp = (char *)malloc(FORMAT_TEMP_SIZE); // todo : check malloc assert(temp); } int n = vsnprintf(temp, FORMAT_TEMP_SIZE, format, ap); if (n >= FORMAT_TEMP_SIZE) { int sz = FORMAT_TEMP_SIZE * 2; for (;;) { result = malloc(sz); // todo : check malloc assert(result); n = vsnprintf(result, sz, format, ap); if (n >= sz) { free(result); sz *= 2; } else { break; } } } else { result = temp; } cstring r = (cstring)malloc(sizeof(struct cstring_data) + n + 1); // todo : check malloc assert(r); r->cstr = (char *)(r+1); r->type = 0; r->ref = 1; r->hash_size = 0; memcpy(r->cstr, result, n+1); if (temp != result) { free(result); } // save temp atomic if (!__sync_bool_compare_and_swap(&cache, NULL, temp)) { free(temp); } else { } return r; } cstring cstring_printf(cstring_buffer sb, const char * format, ...) { cstring s = sb->str; va_list ap; va_start(ap, format); if (s->type == CSTRING_ONSTACK) { int n = vsnprintf(s->cstr, CSTRING_STACK_SIZE, format, ap); if (n >= CSTRING_STACK_SIZE) { s = cstring_format(format, ap); sb->str = s; } else { s->hash_size = n; } } else { cstring_release(sb->str); s = cstring_format(format, ap); sb->str = s; } va_end(ap); return s; }
上面就是 cstring.c的源碼 . 總的而言還是比較短的容易理解 ,我們依次分析. 扯一點,
他這些技巧我都會,還敲了兩三遍. 因為他用的比我熟練.假如你看到這, 你需要 敲更多,才能掌握,C的技巧也挺難的.真的
搞起來就和算術公式一樣.
首先分析數據結構,基本就是流水賬了
/** * todo insert explain * * FORMAT_TEMP_SIZE 是後面函數 cstring_format 分配內存初始化大小 1k * * INTERNING_POOL_SIZE 表示 符號表池的大小 1k * * HASH_START_SIZE hash操作在expand中使用,插入hash使用,是 a mod b 中b的初始化值 */ #define FORMAT_TEMP_SIZE 1024 #define INTERNING_POOL_SIZE 1024 // HASH_START_SIZE must be 2 pow #define HASH_START_SIZE 16 /** * todo insert explain * * 這是一個字符串鏈表, hash采用桶和鏈表實現,這就是鏈表. * char buffer[CSTRING_INTERNING_SIZE];內存在棧上 ,直接 給 string_node.str.cstr * struct string_node保存運行中字符串,直接和 程序同生存周期 */ struct string_node { struct cstring_data str; char buffer[CSTRING_INTERNING_SIZE]; struct string_node * next; }; /* * todo insert explain * * string_node 的 池,這個吃的大小是固定的,1k,過了程序會異常 */ struct string_pool { struct string_node node[INTERNING_POOL_SIZE]; }; /** * 這是 字符串 池 * * lock 加鎖用的 * size hash的大小,圍繞他取余做hash * hash 保存字符串的對象 * pool 符號表存儲的地方 * index 內部指示 pool使用到哪了 * total 指示當前 string_interning 中保存了多少 字符串運行時常量 */ struct string_interning { int lock; int size; struct string_node ** hash; struct string_pool * pool; int index; int total; }; // 全局臨時用的 字符串池對象 static struct string_interning S; // 加鎖 static inline void LOCK() { while (__sync_lock_test_and_set(&(S.lock), 1)) {} } //解鎖 具體參照 原子參照 static inline void UNLOCK() { __sync_lock_release(&(S.lock)); }
這裡擴展一下 就相當於吐槽,首先 關於
S.index 用法 局限性很大
if (si->pool == NULL) { // need not free pool //todo : check memory alloc error si->pool = malloc(sizeof(struct string_pool)); assert(si->pool); si->index = 0; } n = &si->pool->node[si->index++];
全局只用++,相當於只生產字符串,只增不減. 超過了 1024 程序就崩了. 內存訪問越界. 這裡 我們在下一篇博文中
重構這個字符串.思路有兩個
1. 打錯誤日志, 加大錯誤 警報作用
2. 改變 string_interning 結構, 讓其也支持自動擴容處理.
吐槽一下, 他這裡 寫的不好, 這樣的代碼 , 根本不敢掛上服務器跑. 到時候再優化,保證讓其從玩具變成高級玩具.
這裡 再吐槽一下 關於 gcc inline 用法. 具體看 推薦的 參照資料. inline 只能用於簡單的 順序結構函數.
否則 這樣聲明編譯器也還是讓其 變為 普通函數.
測試如下,采用window 匯編,linux 也一樣 通過 gcc -S 看 匯編.
測試 main.c 如下:
#include <stdio.h> #include <stdlib.h> int g_cut = 0; __inline void cnotcplusplus(void) { for (int i = 0; i < 10; ++i) ++g_cut; //測試三 VS能夠使用內聯 //++ g_cut; } int main(int argc, char* argv[]) { printf("g_cut = %d\n", g_cut); /* *測試一 */ for (int i = 0; i < 10; ++i) ++g_cut; /* * 測試二 內聯函數 匯編代碼對比 */ cnotcplusplus(); printf("g_cut = %d\n", g_cut); system("pause"); return 0; }
編譯環境是
這裡運行打斷點 查看 匯編 如下
--- h:\vs_project\clouwu_string\test_inline\main.c ----------------------------- printf("g_cut = %d\n", g_cut); 012A1040 push dword ptr [g_cut (012A3374h)] 012A1046 push offset string "g_cut = %d\n" (012A2108h) 012A104B call printf (012A1010h) /* *測試一 */ for (int i = 0; i < 10; ++i) ++g_cut; 012A1050 mov eax,dword ptr [g_cut (012A3374h)] /* * 測試二 內聯函數 匯編代碼對比 */ cnotcplusplus(); 012A1055 add eax,14h printf("g_cut = %d\n", g_cut); 012A1058 push eax 012A1059 push offset string "g_cut = %d\n" (012A2108h) /* * 測試二 內聯函數 匯編代碼對比 */ cnotcplusplus(); 012A105E mov dword ptr [g_cut (012A3374h)],eax printf("g_cut = %d\n", g_cut); 012A1063 call printf (012A1010h) system("pause"); 012A1068 push offset string "pause" (012A2114h) system("pause"); 012A106D call dword ptr [__imp__system (012A2064h)] 012A1073 add esp,14h return 0; 012A1076 xor eax,eax }
大家可以自己測試測試,這裡測試結果 關於 inline 函數中出現 while 編譯器 會將這個 inline函數當做 普通函數處理.
這是一個失誤,如果 一定要這麼干, 可以用 宏代替,下一個版本再搞. 這個字符串博文拖得太長了,准備就當下 草草干掉了. 爭取下一個版本
帶來一個高級的玩具.
/* * 將字符串結點n 插入 到 字符串hash表中 * * h & (sz -1) => h mod sz, 當然 sz 必須 是 2 正整數次冪 */ static inline void insert_node(struct string_node ** hash, int sz, string_node *n) ; /* * 對 S 中初始化或 擴容的函數 * 會重新hash操作,調整結構 */ static void expand(struct string_interning * si); /* * 插入 一個字符串到 字符串池中,線程安全,運行時不安全 */ static cstring cstring_interning(const char * cstr, size_t sz, uint32_t hash); /* * js hash 命中率 為 80%左右 * 這裡 對於 h == 0,即hash_size == 0做特殊處理,待定,沒有比較久不需要生成 */ static uint32_t hash_blob(const char * buffer, size_t len);
這裡簡單說一下對於 expand 中
int new_size = si->size * 2; if (new_size < HASH_START_SIZE) { new_size = HASH_START_SIZE; }
第二個判斷可以省略,放在 S 的聲明的初始化中. 其它的改進不提了,自己看,多看看都能有見解. 這裡擴展一下, 剛對 他的寫的代碼.
還是 能感覺一股鋪面而來的代碼 美感, 能感覺都很多都是手工敲的. 他的美感 多於他的失誤. 但是 這樣的代碼是不能用在實戰中,為什麼呢,
你用這個代碼,你看幾遍這個源碼.用起來內存洩露的都無法控制了. 這也是很多開源的代碼,不是領頭羊,很少敢直接用於 核心模塊中,除非 別無選擇.
因為 維護的人沒時間, 而自己不是作者,改起來成本比開發一個更高.總而言之,開源和封閉 是 兩種模式 最鮮明對比就是 安卓 與 ios.
繼續,
/* * 釋放永久串, 相當於 釋放 'static' string */ void cstring_free_persist(cstring s) ; /* * 這裡 復制一份字符串, * type =0 , 表示 未初始化, 這樣串可以可以被 cstring_release釋放 */ static cstring cstring_clone(const char * cstr, size_t sz) ; /* * 申請這樣的永久的串 */ cstring cstring_persist(const char * cstr, size_t sz) ; /* * 將棧上串 搞一份出來到 普通串中需要調用 釋放函數 CSTRING_CLOSE * 如果 是 永久串 直接 變成 引用加1 */ cstring cstring_grab(cstring s) ; /* * 釋放函數.只有 type == 0 && s->ref == 1才去釋放 */ void cstring_release(cstring s);
這裡再擴展一下 看 cstring.h 中宏
#define CSTRING_LITERAL(var, cstr) \ static cstring var = NULL; \ if (var) {} else { \ cstring tmp = cstring_persist(""cstr, (sizeof(cstr)/sizeof(char)-1)); \ if(!__sync_bool_compare_and_swap(&var, NULL, tmp)){ \ cstring_free_persist(tmp); \ } \ }
這也是個技巧,如何創建 靜態 堆上 常用變量. 相當於 通過 CSTRING_LITERAL 創建 靜態的字符串 var 變量
並聲明了內存. 雲風就是在秀技巧的. 反正也是學到了,下次自己也用用.
後面還有字符串拼接,這就是 相當於 字符串重構,性能差. 也是為什麼要有 StringBuilder 的原因
/** * * 就是一個簡單的字符串拼接函數,比較低效 * * 這裡有個優化 是 如果串比較直接放在棧上 */ static cstring cstring_cat2(const char *a, const char * b) ; /* * 先自己拼接一下 啥也沒解決 最後給 cstring_cat2 */ cstring cstring_cat(cstring_buffer sb, const char *str) ;
更多的細節 需要看 源碼. 最後 分析 string_printf 函數
/* * 找到固定大小 塞數據進來 */ static cstring cstring_format(const char * format, va_list ap) ; /* * 按照標准輸出到 sb中,sb內存不夠會給它分配 通過 cstring_format */ cstring cstring_printf(cstring_buffer sb, const char *format, ...) ;
到這裡 源碼 是看完了.
多寫幾遍都明白了. 不好意思 太晚了不擴展了,(後面有點忽悠了, 明天還要上班.)
後記
到這裡 錯誤是難免的,歡迎指正立馬改. 下一篇中 會對cstring 進行重構. 解決 雲風的坑.
最後想說一句, char * 夠用了, 真的,C開發沒有那麼多復雜的.
我很水,但我 會改正的, 歡迎批評.