题外话

  以前也用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

前言

  这次直接切入正题,也许你会不屑一顾,还是想 分享一个故事

面朝大海,春暖花开
海子 于
从明天起,做一个幸福的人
喂马、劈柴,周游世界
从明天起,关心粮食和蔬菜
我有一所房子,面朝大海,春暖花开 从明天起,和每一个亲人通信
告诉他们我的幸福
那幸福的闪电告诉我的
我将告诉每一个人 给每一条河每一座山取一个温暖的名字
陌生人,我也为你祝福
愿你有一个灿烂的前程
愿你有情人终成眷属
愿你在尘世获得幸福
我只愿面朝大海,春暖花开

同名歌曲

面朝大海 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[]; typedef struct cstring_data * cstring; #define CSTRING_BUFFER(var) \
char var##_cstring [CSTRING_STACK_SIZE] = { '\0' }; \
struct cstring_data var##_cstring_data = { var##_cstring , , CSTRING_ONSTACK, }; \
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))-); \
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 != ) {} 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, , )))
#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[];

上面 定义的 cstring_buffer 类型,是C中一个声明技巧

例如 cstring_buffer cb; 其中 cb 内存分配在 栈上 但是可以当指针使用,传入到 struct cstring_buffer* 地方.

更简单一点如下,其它就自己悟吧

cstring_buffer cb; => struct cstring_buffer cb[];

3.1 函数宏分析

#define CSTRING_BUFFER(var) \
char var##_cstring[CSTRING_STACK_SIZE] = { '\0' }; \
struct cstring_data var##_cstring_data = { var##_cstring, , CSTRING_ONSTACK, }; \
cstring_buffer var; \
var->str = &var##cstring_data;

这个宏 也很巧妙, ## 表示链接宏 假如 var 是 abc,var只能同变量,不能有双引号

那么就声明了一个

char abc_cstring[] = { '\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)-)); \
if(!__sync_bool_compare_and_swap(&var, NULL, tmp)){ \
cstring_free_persist(tmp); \
} \
}

这个函数宏声明变量都是 全局存储区的变量,这里认为是常量cstring.

但是cstr必须是 引起""引起宏.

这里这个

cstring_persist(""cstr, (sizeof(cstr)/sizeof(char)-)); 

""特别亮,在函数编译的时候就能找出错误!第二参数 常量字符串最后一个字符的索引

后面 __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, , )))
#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 + );
// todo: memory alloc error
assert(p);
void * ptr = (void *)(p + );
p->cstr = ptr;
p->type = ;
p->ref = ;
memcpy(ptr, cstr, sz);
((char*)ptr)[sz] = '\0';
p->hash_size = ;

这里的一个技巧是 直接一次 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 + );

下面这种方式和上面比有点 内存更小了, 小了 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, , CSTRING_ONSTACK, }; \
cstring_buffer var; \
var->str = &var##_cstring_data;

这里 再扩展一下,吐槽一下 云风前辈

    struct cstring_data *p = malloc(sizeof(struct cstring_data) + sz + );
// 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 + );
// 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",);
printf("%s\n", CSTRING(a)->cstr);
CSTRING_CLOSE(a);
cstring_release(b);
} int
main() {
test();
return ;
}

我们采用 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 != ) {} 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",);

变成待释放的临时串  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),)) {}
} 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-);
n->next = hash[index];
hash[index] = n;
} static void
expand(struct string_interning * si) {
int new_size = si->size * ;
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, , sizeof(struct string_node *) * new_size);
int i;
for (i=;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-));
struct string_node * n = si->hash[index];
while(n) {
if (n->str.hash_size == hash) {
if (strcmp(n->str.cstr, cstr) == ) {
return &n->str;
}
}
n = n->next;
}
// 80% (4/5) threshold
if (si->total * >= si->size * ) {
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 = ;
}
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 = ; 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>>)+;
size_t i;
for (i=len; i>=step; i-=step)
h = h ^ ((h<<)+(h>>)+ptr[i-]);
if (h == )
return ;
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 + );
// todo: memory alloc error
assert(p);
void * ptr = (void *)(p + );
p->cstr = ptr;
p->type = ;
p->ref = ;
memcpy(ptr, cstr, sz);
((char *)ptr)[sz] = '\0';
p->hash_size = ;
return p;
} cstring
cstring_persist(const char * cstr, size_t sz) {
cstring s = cstring_clone(cstr, sz);
if (s->type == ) {
s->type = CSTRING_PERMANENT;
s->ref = ;
}
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 == ) {
s->type = CSTRING_PERMANENT;
} else {
__sync_add_and_fetch(&s->ref,);
}
return s;
}
} void
cstring_release(cstring s) {
if (s->type != ) {
return;
}
if (s->ref == ) {
return;
}
if (__sync_sub_and_fetch(&s->ref,) == ) {
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 == ) {
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 ;
if ((a->type == CSTRING_INTERNING) &&
(b->type == CSTRING_INTERNING)) {
return ;
}
if ((a->type == CSTRING_ONSTACK) &&
(b->type == CSTRING_ONSTACK)) {
if (a->hash_size != b->hash_size) {
return ;
}
return memcmp(a->cstr, b->cstr, a->hash_size) == ;
}
uint32_t hasha = cstring_hash(a);
uint32_t hashb = cstring_hash(b);
if (hasha != hashb) {
return ;
}
return strcmp(a->cstr, b->cstr) == ;
} 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 + );
// todo: memory alloc error
assert(p);
char * ptr = (char *)(p + );
p->cstr = ptr;
p->type = ;
p->ref = ;
memcpy(ptr, a, sa);
memcpy(ptr+sa, b, sb);
ptr[sa+sb] = '\0';
p->hash_size = ;
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-) {
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 * ;
for (;;) {
result = malloc(sz);
// todo : check malloc
assert(result);
n = vsnprintf(result, sz, format, ap);
if (n >= sz) {
free(result);
sz *= ;
} else {
break;
}
}
} else {
result = temp;
}
cstring r = (cstring)malloc(sizeof(struct cstring_data) + n + );
// todo : check malloc
assert(r);
r->cstr = (char *)(r+);
r->type = ;
r->ref = ;
r->hash_size = ;
memcpy(r->cstr, result, n+);
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), )) {}
} //解锁 具体参照 原子参照
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 = ;
}
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 = ; __inline void cnotcplusplus(void)
{
for (int i = ; i < ; ++i)
++g_cut; //测试三 VS能够使用内联
//++ g_cut;
} int main(int argc, char* argv[])
{ printf("g_cut = %d\n", g_cut); /*
*测试一
*/
for (int i = ; i < ; ++i)
++g_cut; /*
* 测试二 内联函数 汇编代码对比
*/
cnotcplusplus(); printf("g_cut = %d\n", g_cut); system("pause");
return ;
}

编译环境是

这里运行打断点 查看 汇编 如下

--- 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 = ; i < ; ++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 ;
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 * ;
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)-)); \
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开发没有那么多复杂的.

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

对 cloudwu 简单的 cstring 进行简单解析的更多相关文章

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

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

  2. python模块介绍- HTMLParser 简单的HTML和XHTML解析器

    python模块介绍- HTMLParser 简单的HTML和XHTML解析器 2013-09-11 磁针石 #承接软件自动化实施与培训等gtalk:ouyangchongwu#gmail.comqq ...

  3. 单例模式、简单工厂模式、XML解析

    单例模式: 什么是单例模式? 针对特定问题提出的特定解决方案 为什么使用设计模式? 让程序有更好的可扩展性 在哪里使用? 一般情况下,开发中真正使用设计模式的地方,JVM(虚拟机)底层机制模式 usi ...

  4. 对云风 cstring 第二次解析

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

  5. 学python2.7简单还是python3.0简单,两者区别

    学python2.7简单还是python3.0简单,谈谈两者区别 1. 使用__future__模块 Python 3.X 引入了一些与Python 2 不兼容的关键字和特性.在Python 2中,可 ...

  6. 【转】C#类的分类(静态类、实例类、嵌套类、结构、简单的抽象类、简单的密封类)

    静态类 -------------------------------------------------------------------------------- 静态类就是在class关键字前 ...

  7. CORS跨域请求之简单请求与非简单请求

    先来看一个例子 定义server01的项目,在路由表中添加一条路由记录 url(r'^getData.html$',views.get_data) 对应的视图函数 from django.shortc ...

  8. Python入门一:简单得不能再简单了##

    从python的语法上看,简单得不能再简单了. 想学它,请移步廖雪峰python2.7教程以及python3.这实在是最好的入门教程.参考资料太多: 外国的教程 Python 入门指南 Python ...

  9. 要多简单就有多简单的H5拍照加水印

    来一个简单粗暴的gif演示图 先来html 内容 <video id="video" width="320" height="240" ...

随机推荐

  1. CODESOFT对话框中的显示字体怎么修改

      不同的人其使用软件的视觉习惯是不一样的,直接给大家介绍过如何设置CODESOFT的界面语言,这是一个大范围的界面显示设置.本文,将介绍如何修改CODESOFT对话框显示的字体,以满足自己的视觉习惯 ...

  2. OpenGL ES为缓存提供数据的7个步骤

    OpenGL ES为缓存提供数据的7个步骤: 1.生成glGenBuffers()——请求OpenGL ES为图形处理器控制的缓存生成一个独一无二的标识符. 2.绑定glBindBuffer()——告 ...

  3. 洛谷P1475 控制公司 Controlling Companies

    P1475 控制公司 Controlling Companies 66通过 158提交 题目提供者该用户不存在 标签USACO 难度普及/提高- 提交  讨论  题解 最新讨论 暂时没有讨论 题目描述 ...

  4. Java基础——异常体系

    在Java中,异常对象都是派生于Throwable类的一个实例,Java的异常体系如下图所示: 所有的异常都是由Throwable继承而来,在下一层立即分解为两个分支,Error和Exception. ...

  5. SendKeys回车操作类

    /************************************************************ FileName: SendKey.cs Description: 模拟键盘 ...

  6. HTML5--》details

    <details>是HTML5的新标签,用于描述文档或文档某个部分的细节. 目前只有 Chrome 和 Safari 6 支持 <details> 标签. 与 <summ ...

  7. 开源项目:网页实时通信WebRTC

    参考资料 [博客系列] Android WebRTC 音视频开发总结 [环境编译] WebRTC入门指南 各平台WebRTC源码网盘下载 Ubuntu14.04编译WebRTC For Android ...

  8. 【Python】django安装

    官方下载:https://www.djangoproject.com/download/ 报错 [root@test Django-]# python setup.py install Traceba ...

  9. c++ boost--------------boost::progress_timer小试。

    //--------------------------------------------------------------------------- #include <vcl.h> ...

  10. 如何查找Mac上的USB存储设备使用痕迹

    最近刚好有个案子的证物主机是MBP, OS X版本为El Capitan,案况与营业秘密外泄有关,当中要找有关USB存储设备的使用痕迹. 要提醒大家的是,不同版本的OS X,各种迹证的存放文件名称及路 ...