题外话

  以前也用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 128struct 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 errorassert(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 16struct 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) thresholdif (si->total * 5 >= si->size * 4) {return NULL;}if (si->pool == NULL) {// need not free pool// todo: check memory alloc errorsi->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;elsereturn 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 atomicif (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 atomicif (!__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 errorsi->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开发没有那么多复杂的.

我很水,但我 会改正的, 欢迎批评.

转载于:https://www.cnblogs.com/life2refuel/p/5043578.html

对 cloudwu 简单的 cstring 进行简单解析相关推荐

  1. 机器学习入门案例简单理解——Tensorflow之MNIST解析

    深度学习简单介绍 首先要简单区别几个概念:人工智能,机器学习,深度学习,神经网络.这几个词应该是出现的最为频繁的,但是他们有什么区别呢? 人工智能:人类通过直觉可以解决的问题,如:自然语言理解,图像识 ...

  2. 基于简单sql语句的sql解析原理及在大数据中的应用

    基于简单sql语句的sql解析原理及在大数据中的应用 李万鸿 老百姓呼吁打土豪分田地.共同富裕,总有一天会实现. 全面了解你所不知道的外星人和宇宙真想:http://pan.baidu.com/s/1 ...

  3. 全网最简单的C# json数据解析 无敌菜鸟教程 十分钟搞定json数据解析

    全网最简单的C# json数据解析 无敌菜鸟教程 十分钟搞定json数据解析 废话先说一点,算了.直接进入正题吧. 用例子说话: JSON数据: string json: " {" ...

  4. 使用ANTLR做一个简单的Python SQL语法解析器 - 推酷

    使用ANTLR做一个简单的Python SQL语法解析器 - 推酷 使用ANTLR做一个简单的Python SQL语法解析器 - 推酷 posted on 2016-11-14 13:11 lexus ...

  5. 【Rust日报】2022-03-22 fluent-uri:一个快速、简单和严格的URI解析器

    fluent-uri:一个快速.简单和严格的URI解析器 Announcing fluent-uri: a fast, easy and strict URI parser 简介 一个严格遵守IETF ...

  6. 对云风 cstring 第二次解析

    前言 从明天起 关心粮食和蔬菜 我有一所房子 面朝大海 春暖花开 本文前提条件 1.了解 posix 线程 2.了解 原子操作 3.具备简单C基础,或者 你也敲一遍. 如果上面不太清楚,你可以翻看我以 ...

  7. EasyJoyStick使用以及两种操作杆 EasyJoyStick的使用方法,简单的不能再简单 Hedgehog Team-》Easy Touch -》Add Easy Touch For C#

    EasyJoyStick使用以及两种操作杆 EasyJoyStick的使用方法,简单的不能再简单 Hedgehog Team->Easy Touch ->Add Easy Touch Fo ...

  8. 小白前端之路:手写一个简单的vue-router这几年,好像过的好快,怀念我的大学生活。 - 连某人 大三实习生,之前写过简单MVVM框架、简单的vuex、但是看了vue-router的源码(看了

    这几年,好像过的好快,怀念我的大学生活. 连某人 大三实习生,之前写过简单MVVM框架.简单的vuex.但是看了vue-router的源码(看了大概)之后就没有写,趁着周末不用工作(大三趁着不开学出来 ...

  9. 简单点儿、简单点儿、再简单点儿,其实世界可以不是我们想象的那么复杂

    一直抱着完美的架构理念埋头苦干了很多年,终于初步完成了通用权限的第一个稳定版本,也有各种各样几百个付费客户,也多多少少听到很多用户的反馈,虽然刚开始有些听不进去但是多想想人家还是说得很有道理. 按编程 ...

  10. (教学思路 C#之类五)类的分类(静态类、实例类、嵌套类、结构、简单的抽象类、简单的密封类)...

    本节课我们来学习类的扩展分类,看我的标题中就罗列出了类的分类,类按照声明方式和功能定义共分为这六种类,实例类就是我们一直在用的类,class关键字前没有其他的修饰符,之前已经有很多实例类的使用方法了, ...

最新文章

  1. asp 取数据 计算_地学数据 | 地理空间数据获取方式汇总
  2. linux查看openssh和openssl版本
  3. 在golang中defer、panic与recover的作用
  4. mysql动态top_MySQL 之 MyTop实时监控MySQL
  5. 举枪消灭烂代码的实战案例
  6. 设计模式学习--工厂模式
  7. java中logger关闭log_Log4J如何关闭Logger对象的IO流资源
  8. scala中的数组的转换操作
  9. linux暂停和恢复进程
  10. C++ OpenCV 开发笔记整理分类合集【所有的相关记录,都整理在此】
  11. redis memcached 可视化管理及监控工具 TreeNMS
  12. go get失败时怎么办(golang.org/x/crypto/md4为例)
  13. webgame源码下载及网页游戏开发资源精华集合
  14. 基于埋点日志数据的网络流量统计(网站独立访客数(UV)的统计)
  15. 【程序风云】程序人生风云
  16. python随机森林筛选变量_用随机森林分类器和GBDT进行特征筛选
  17. 用数学计算1.01的365次方等于37.8来说明积跬步至千里是否合理? 1.01^365=37.8 0.99^365=0.03
  18. Contest Record
  19. 3万字长文,PySpark入门级学习教程,框架思维
  20. OA系统-收入支出模块

热门文章

  1. 构建根文件系统_Linux之构建文件系统
  2. PHP和zookeeper结合实践
  3. [Python设计模式] 第25章 联合国维护世界和平——中介者模式
  4. L2-009. 抢红包
  5. 简单构建一个xmlhttp对象池合理创建和使用xmlhttp对象
  6. ORACLE 常用函数——日期/时间函数
  7. 那些不知所措的运算符优先级
  8. 09月23日 pytorch与resnet(一)
  9. Practical Lessons from Predicting Clicks on Ads at Facebook
  10. 第二季-专题4-我是bootloader设计师