程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> 對 cloudwu 簡單的 cstring 進行簡單解析,cloudwucstring

對 cloudwu 簡單的 cstring 進行簡單解析,cloudwucstring

編輯:關於C語言

對 cloudwu 簡單的 cstring 進行簡單解析,cloudwucstring


題外話

  以前也用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開發沒有那麼多復雜的.

我很水,但我 會改正的, 歡迎批評.

 

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