当前位置: 首页 > 编程日记 > 正文

对云风 cstring 第二次解析

前言

        

    从明天起 关心粮食和蔬菜

      我有一所房子 面朝大海 春暖花开

本文前提条件

  1.了解 posix 线程

  2.了解 原子操作

  3.具备简单C基础,或者 你也敲一遍.

如果上面不太清楚,你可以翻看我以前的博客,或者'百度'搜索.

结论

  1.云风前辈的 玩具 cstring 有点坑, 内存管理很随意(也可能时我菜,理解不了他飘逸的写法)

  2.对于江湖中成名已久的 高手, 其实 胜在 思路上.

  3.前辈3-4h搞完的,重构了1周, 发现 const char* 和 char* 够用了,真的,越简单越针对 , 越好,学习成本越低

对简单开源代码有兴趣可以看看,毕竟开源的不都是好的.这里做的工作就是简单和扩平台,简单可用升级,如果你也对C字符串感兴趣

可以看看,否则没有必要.

正文

  到这里扯皮结束了, 最近任务有点多,游戏公司加班太疯狂了,做的越快任务越多.哎. 以前博客可能讲了不少关于cstring 结构设计.

这里就简单扯一点,重构部分. 从整体上讲,细节自己多练习了.

1.跨平台所做的工作

  跨平台主要围绕等待函数和原子操作封装,看下面 的

sc_atom.h 文件内容

#ifndef _SC_ATOM
#define _SC_ATOM/** 作者 : wz* * 描述 : 简单的原子操作,目前只考虑 VS(CL) 小端机 和 gcc*         推荐用 posix 线程库*/// 如果 是 VS 编译器
#if defined(_MSC_VER)#include <Windows.h>//忽略 warning C4047: “==”:“void *”与“LONG”的间接级别不同
#pragma warning(disable:4047) // v 和 a 多 long 这样数据
#define ATOM_FETCH_ADD(v, a) \InterlockedExchangeAdd((LONG*)&(v), (LONG)(a))#define ATOM_ADD_FETCH(v, a) \InterlockedAdd((LONG*)&(v), (LONG)(a))#define ATOM_SET(v, a) \InterlockedExchange((LONG*)&(v), (LONG)(a))#define ATOM_CMP(v, c, a) \(c == InterlockedCompareExchange((LONG*)&(v), (LONG)(a), (LONG)c))/*对于 InterlockedCompareExchange(v, c, a) 等价于下面long tmp = v ; v == a ? v = c : ; return tmp;咱么的 ATOM_FETCH_CMP(v, c, a) 等价于下面long tmp = v ; v == c ? v = a : ; return tmp;*/
#define ATOM_FETCH_CMP(v, c, a) \InterlockedCompareExchange((LONG*)&(v), (LONG)(a), (LONG)c)#define ATOM_LOCK(v) \while(ATOM_SET(v, 1)) \Sleep(0)#define ATOM_UNLOCK(v) \ATOM_SET(v, 0)//否则 如果是 gcc 编译器
#elif defined(__GNUC__)#include <unistd.h>/*type tmp = v ; v += a ; return tmp ;type 可以是 8,16,32,84 的 int/uint*/
#define ATOM_FETCH_ADD(v, a) \__sync_fetch_add_add(&(v), (a))/*v += a ; return v;*/
#define ATOM_ADD_FETCH(v, a) \
__sync_add_and_fetch(&(v), (a))/*type tmp = v ; v = a; return tmp;*/
#define ATOM_SET(v, a) \__sync_lock_test_and_set(&(v), (a))/*bool b = v == c; b ? v=a : ; return b;*/
#define ATOM_CMP(v, c, a) \__sync_bool_compare_and_swap(&(v), (c), (a))/*type tmp = v ; v == c ? v = a : ;  return v;*/
#define ATOM_FETCH_CMP(v, c, a) \__sync_val_compare_and_swap(&(v), (c), (a))/*加锁等待,知道 ATOM_SET 返回合适的值_INT_USLEEP 是操作系统等待纳秒数,可以优化,看具体操作系统使用方式int lock;ATOM_LOCK(lock);//to do think ...ATOM_UNLOCK(lock);*/
#define _INT_USLEEP (2)
#define ATOM_LOCK(v) \while(ATOM_SET(v, 1)) \usleep(_INT_USLEEP)/*对ATOM_LOCK 解锁, 当然 直接调用相当于 v = 0;*/
#define ATOM_UNLOCK(v) \__sync_lock_release(&(v))#endif /*!_MSC_VER && !__GNUC__ */#endif /*!_SC_ATOM*/

这里就是统一简单包装gcc 和 VS中提供的 gcc操作.

这里需要说明一下, gcc 中 __sync__... 是基于编译器层的 操作. 而 VS中Interlock... 是基于 Windows api的

有很大不同,这里也只是简单揉了一下,能用的相似的部分.例如

//忽略 warning C4047: “==”:“void *”与“LONG”的间接级别不同
#pragma warning(disable:4047) // v 和 a 多 long 这样数据
#define ATOM_FETCH_ADD(v, a) \InterlockedExchangeAdd((LONG*)&(v), (LONG)(a))

主要是防止VS 警告和编译器不通过而改的. v类型不知道而 InterlockedExchangeAdd 只接受 LONG参数.

2.本土个人化接口文件定义

主要见sc_string.h 文件

#ifndef _H_SC_STRING
#define _H_SC_STRING#include <stdint.h>
#include <stddef.h>
#include "sc_atom.h"#define _INT_STRING_PERMANENT    (1)                //标识 字符串是持久的相当于static
#define _INT_STRING_INTERNING    (2)                //标识 字符串在运行时中,和内存同生死
#define _INT_STRING_ONSTACK        (4)                //标识 字符串分配在栈上//0 潜在 标识,这个字符串可以被回收,游离态#define _INT_INTERNING            (32)            //符号表 字符串大小
#define _INT_ONSTACK            (128)            //栈上内存大小struct cstring_data {char* cstr;                                 //保存字符串的内容uint32_t hash;                                //字符串hash,如果是栈上的保存大小uint16_t type;                                //主要看 _INT_STRING_* 宏,默认0表示临时串uint16_t ref;                                //引用的个数, 在 type == 0时候才有用
};typedef struct _cstring_buffer {struct cstring_data* str;
} cstring_buffer[1];                            //这个cstring_buffer是一个在栈上分配的的指针类型

typedef struct cstring_data* cstring;            //给外部用的字符串类型/** v : 是一个变量名** 构建一个 分配在栈上的字符串.* 对于 cstring_buffer 临时串,都需要用这个 宏声明创建声明,* 之后可以用 CSTRING_CLOSE 关闭和销毁这个变量,防止这个变量变成临时串*/
#define CSTRING_BUFFER(v) \char v##_cstring[_INT_ONSTACK] = { '\0' }; \struct cstring_data v##_cstring_data = { v##_cstring, 0, _INT_STRING_ONSTACK, 0 }; \cstring_buffer v; \v->str = &v##_cstring_data;/** v : CSTRING_BUFFER 声明的字符串变量* 释放字符串v,最好成对出现,创建和销毁*/
#define CSTRING_CLOSE(v) \if(0 == (v)->str->type) \cstring_release((v)->str)/** s : cstring_buffer 类型* 方便直接访问 struct cstring_data 变量*/
#define CSTRING(s) ((s)->str)/** v    : 声明的常量名,不需要双引号* cstr : 常量字符串,必须是用 ""括起来的*/
#define CSTRING_LITERAL(v, cstr) \static cstring v; \if (NULL == v) { \cstring tmp = cstring_persist(""cstr, ( sizeof(cstr)/sizeof(char) - 1 )); \if(!ATOM_CMP(v, NULL, tmp)) { \cstring_free_persist(tmp); \} \}/* low level api, don't use directly */
cstring cstring_persist(const char* cstr, size_t sz);
void cstring_free_persist(cstring s);/*public api*/
/** s        : 待处理的串* return    : 处理后永久串,可以返回或使用 * 主要将栈上的串拷贝到临时堆上或者将临时堆待释放的串变到符号表中*/
extern cstring cstring_grab(cstring s);/** s : 待释放的串* 主要是对临时堆上的串进行引用计数删除*/
extern void cstring_release(cstring s);/** sb        : 字符串保存对象* str        : 拼接的右边字符串 * return    : 返回拼接好的串 cstring*/
extern cstring cstring_cat(cstring_buffer sb, const char* str);/** sb        : 字符串'池' , 这个字符串库维护,你只管用* format    : 格式化串,按照这个格式化输出内容到 sb 中* ...        : 可变参数内容* return    : 格式化好的字符串,需要自己释放** 后面 __attribute format 是在gcc上优化编译行为,按照printf编译约束来*/
extern cstring cstring_printf(cstring_buffer sb, const char* format, ...)
#ifdef __GNUC____attribute__((format(printf, 2, 3)))
#endif
;/** a        : 字符串a* b        : 字符串b* return    : 当a和b不同是直接返回false,相同需要多次比较,相比strcmp 好一些*/
extern int cstring_equal(cstring a, cstring b);/** s        : 字符串s* 为字符串s 生成hash值并返回,除了栈上的会设置上这个hash值*/
extern uint32_t cstring_hash(cstring s);// 临时补得一个 日志宏,方便查错,推荐这些接口 用日志系统代替,是一个整体
#ifndef cerr
#include <stdio.h>
/** 错误处理宏,msg必须是""括起来的字符串常量* __FILE__        : 文件全路径* __func__        : 函数名* __LINE__        : 行数行* __VA_ARGS__    : 可变参数宏,* ##表示直接连接, 例如 a##b <=> ab*/
#define cerr(msg,...) \fprintf(stderr, "[%s:%s:%d]" msg "\n",__FILE__,__func__,__LINE__,##__VA_ARGS__)
#endif#endif /*!_H_SC_STRING*/

以上是重构的所有接口,其实就是换皮了.外加了一些解释. 后面添加了简单测试宏. 以后在项目中换成内部日志系统.

3.接口文件实现

接口实现文件内容多一点

sc_string.c

#include "sc_string.h"#include <stdlib.h>
#include <stdarg.h>
#include <string.h>#define _INT_FORMAT_TEMP    (1024)
// 这样做也是治标不治本,保存2k个字符串常量
#define _INT_INTERNING_POOL (2048)
// hash size must be 2 pow
#define _INT_HASH_START 8/** 字符串结点,可以认为是一个桶,链表* str  : 字符串具体变量* buf  : 临时栈上变量,主要为 str.str 用的* next : 下一个字符串结点*/
struct string_node {struct cstring_data str;char buf[_INT_INTERNING];struct string_node* next;
};/** 认为是字符串池,主要保存运行时段字符串变量,存在上限,因系统而定*/
struct string_pool {struct string_node node[_INT_INTERNING_POOL];
};/** 字符串对象的管理器* * lock  : 加锁用的* size  : hash串的大小* hash  : 串变量* total : 当前string_interning 中保存的字符串运行时常量* pool  : 符号表存储的地方* index : 标识pool 堆上保存到哪了*/
struct string_interning {int lock;int size;struct string_node** hash;int total;struct string_pool* pool;int index;
};// 总的字符串管理对象实例化
static struct string_interning __sman = {0, _INT_HASH_START, NULL, 0, NULL, 0
};// 这个 sc_string.c 用到的加锁解锁简化的 宏
#define LOCK() \ATOM_LOCK(__sman.lock)#define UNLOCK() \ATOM_UNLOCK(__sman.lock)/** 将字符串结点插入到hash表中** struct string_node** hash : 指向字符串链表结点指针的指针,认为是hash表* int sz : 新的hash表大小,上面指针的大小,这个值必须是 2的幂* struct string_node* n : 待插入hash表的结点*/
static void __insert_node(struct string_node** hash, int sz, struct string_node* n)
{uint32_t h = n->str.hash;int idx = h & (sz - 1);n->next = hash[idx];hash[idx] = n;
}/** 为 运行时的 字符串 struct string_interning 变量扩容,重新hash分配* * struct string_interning* si : 字符串池总对象*/
static void __expand(struct string_interning* si)
{int nsize = si->size << 1; //简单扩容struct string_node** nhash = calloc(nsize, sizeof(struct string_node*));if (NULL == nhash) {cerr("nhash calloc run error, memory insufficient.");exit(EXIT_FAILURE);}if (si->size > _INT_HASH_START) {for (int i = 0; i < si->size; ++i) {struct string_node* node = si->hash[i];while (node) { // 头结点会变成尾结点struct string_node* tmp = node->next;__insert_node(nhash, nsize, node);node = tmp;}}}//释放原先内存,重新回来free(si->hash);si->hash = nhash;si->size = nsize;
}/** 创建一个运行时字符串对象并返回,理解为字符串常量.不需要释放* * si    : 总的字符串对象* cstr    : 普通字符串量* sz    : cstr需要的处理的长度,这个参数 必须 < _INT_INTERNING* hs    : 这个字符串cstr的 hs值**        : 返回值 是一个常量字符串的地址,有直接返回,没有构建*/
static cstring __interning(struct string_interning* si, const char* cstr, size_t sz, uint32_t hs)
{//si中hash表为NULL,保存无意义if (NULL == si->hash)return NULL;int sse = si->size;int idx = hs & (sse - 1);struct string_node* n = si->hash[idx];while (n) {if (n->str.hash == hs) if (strcmp(n->str.cstr, cstr) == 0) return &n->str;n = n->next;}// 这里采用的 jshash 函数不碰撞率 80% (4/5) , 这是经验代码if (si->total * 5 >= sse * 4)return NULL;if (NULL == si->pool) { //这个不是一个好设计.为了适应struct string_pool*,这种写死的内存块放在可以放在全局区,但是无法扩展// need not free poolsi->pool = malloc(sizeof(struct string_pool));if (NULL == si->pool) {cerr("si->pool malloc run error, memory insufficient.");exit(EXIT_FAILURE);}si->index = 0;}n = &si->pool->node[si->index++];memcpy(n->buf, cstr, sz);n->buf[sz] = '\0'; //cstr 最后是'\0'
cstring cs = &n->str;cs->cstr = n->buf;cs->hash = hs;cs->type = _INT_STRING_INTERNING;cs->ref = 0;n->next = si->hash[idx];si->hash[idx] = n;return cs;
}/** 生成一个字符串常量,主要放在 __sman.pool 中 ** cstr : 待处理的C字符串* sz    : 字符串长度* hs    : 字符串jshash的值*        : 返回 生成的符号字符串的地址*/
static cstring __cstring_interning(const char* cstr, size_t sz, uint32_t hs)
{cstring srt;LOCK();srt = __interning(&__sman, cstr, sz, hs);if (NULL == srt) {__expand(&__sman); //内存扩容srt = __interning(&__sman, cstr, sz, hs);}++__sman.total; //记录当前字符串常量个数
    UNLOCK();return srt;
}/** jshash实现,当返回0设置为1,这里0用作特殊作用,表名初始化状态* * buf    : c字符串* len    : 字符集长度*        : 返回生成的字符串hash值*/
static uint32_t __get_hash(const char* buf, size_t len)
{const uint8_t* ptr = (const uint8_t*)buf;size_t h = len; // hash初始化值size_t step = (len >> 5) + 1;for (size_t i = len; i >= step; i -= step)h ^= ((h<<5) + (h>>2) + ptr[i-1]); //将算法导论中东西直接用return h == 0 ? 1 : h;
}/** 拷贝C字符串,并返回地址** cstr        : c字符串* sz        : cstr中处理的长度*            : 返回当前字符串地址*/
static cstring __cstring_clone(const char* cstr, size_t sz)
{if (sz < _INT_INTERNING)return __cstring_interning(cstr, sz, __get_hash(cstr, sz));//长的串,这里放在堆上struct cstring_data* p = malloc(sizeof(struct cstring_data) + sizeof(char) * (sz + 1));if(NULL == p){cerr("p malloc run error, memory insufficient.");exit(EXIT_FAILURE);}//ptr 指向后面为容纳 cstr申请的内存,并初始化一些量void* ptr = p + 1;p->cstr = ptr;p->type = 0;p->ref = 1;memcpy(ptr, cstr, sz);((char*)ptr)[sz] = '\0';p->hash = 0;return p;
}/* low level api, don't use directly */
cstring 
cstring_persist(const char* cstr, size_t sz)
{cstring s = __cstring_clone(cstr, sz);if (0 == s->type) { //没有放在运行时的常量中s->type = _INT_STRING_PERMANENT; // 标识持久的字符串中s->ref = 0;}return s;
}void 
cstring_free_persist(cstring s) //用完释放,这些api CSTRING_LITERAL宏中自动调用
{if (s->type == _INT_STRING_PERMANENT)free(s);
}cstring 
cstring_grab(cstring s)
{if (s->type & (_INT_STRING_PERMANENT | _INT_STRING_INTERNING))return s;if (s->type == _INT_STRING_ONSTACK)return __cstring_clone(s->cstr, s->hash);// 后面就是临时串 type == 0if (0 == s->ref) //没有引用让其变为持久串,不说内存泄露了,就说已经释放内存能不能用了都是问题s->type = _INT_STRING_PERMANENT;elseATOM_ADD_FETCH(s->ref, 1);return s;
}void 
cstring_release(cstring s)
{if (0 != s->type)return;if (0 == s->ref)return;ATOM_ADD_FETCH(s->ref, -1); //为了兼容 window特别处理if (s->ref == 0)free(s);
}uint32_t
cstring_hash(cstring s) 
{if (_INT_STRING_ONSTACK == s->type)return __get_hash(s->cstr, s->hash);if (0 == s->hash)s->hash = __get_hash(s->cstr, strlen(s->cstr));return s->hash;
}int 
cstring_equal(cstring a, cstring b)
{if (a == b)return 1;//都是运行时的字符串常量,肯定不同if (a->type == _INT_STRING_INTERNING && b->type == _INT_STRING_INTERNING)return 0;if (a->type == _INT_STRING_ONSTACK && b->type == _INT_STRING_ONSTACK) {if (a->hash != b->hash)return 0;return memcmp(a->cstr, b->cstr, a->hash) == 0;}uint32_t ha = cstring_hash(a);uint32_t hb = cstring_hash(b);if (ha != hb) //hash 能够确认不同,但相同不一定同return 0;return strcmp(a->cstr, b->cstr) == 0;
}/** 拼接c串a和b,可以话放在符号表中,大的话放在临时区中** a        : c串a* b        : c串b*            : 返回拼接后的cstring 变量*/
static cstring __cstring_cat(const char* a, const char* b)
{size_t sa = strlen(a);size_t sb = strlen(b);size_t sm = sa + sb;if (sm < _INT_INTERNING) {char tmp[_INT_INTERNING];memcpy(tmp, a, sa);memcpy(tmp + sa, b, sb);tmp[sm] = '\0';return __cstring_interning(tmp, sm, __get_hash(tmp, sm));}//这里同样走 堆上内存分配struct cstring_data* p = malloc(sizeof(struct cstring_data) + sizeof(char) * (sm + 1));if (NULL == p) {cerr("p malloc run error, memory insufficient.");exit(EXIT_FAILURE);}//ptr 指向后面为容纳 cstr申请的内存,并初始化一些量char* ptr = (char*)(p + 1);p->cstr = ptr;p->type = 0;p->ref = 1;memcpy(ptr, a, sa);memcpy(ptr+sa, b, sb);ptr[sm] = '\0';p->hash = 0;return p;
}cstring 
cstring_cat(cstring_buffer sb, const char* str)
{cstring s = sb->str;if (s->type == _INT_STRING_ONSTACK) {int i = (int)s->hash;while (i < _INT_ONSTACK - 1) {s->cstr[i] = *str;if (*str == '\0') //可以就直接返回,全放在栈上return s;++s->hash;++str;++i;}s->cstr[i] = '\0';}// 栈上放不下,那就 试试 放在运行时中cstring tmp = s; sb->str = __cstring_cat(tmp->cstr, str); // 存在代码冗余, _INT_ONSTACK > _INT_INTERNING
    cstring_release(tmp);return sb->str;
}/** 根据模式化字符串,和可变参数拼接字符串,返回最终拼接的cstring 地址** format        : 模板字符串* ap            : 可变参数集*                : 返回拼接后的字符串cstring变量*/
static cstring __cstring_format(const char* format, va_list ap)
{static char* __cache = NULL; //持久化数据,编译器维护char* rt;char* tmp = __cache;// read __cache buffer atomicif (tmp) {//tmp 获取 __cache值, 如果 __cache == tmp ,会让 __cache = NULLtmp = ATOM_FETCH_CMP(__cache, tmp, NULL);}if (NULL == tmp) {tmp = malloc(sizeof(char) * _INT_FORMAT_TEMP);if (NULL == tmp) {cerr("tmp malloc run error, memory insufficient.");exit(EXIT_FAILURE);}}int n = vsnprintf(tmp, _INT_FORMAT_TEMP, format, ap);if (n >= _INT_FORMAT_TEMP) {int sz = _INT_FORMAT_TEMP << 1;for (;;) {rt = malloc(sizeof(char)*sz);if (NULL == rt) {cerr("rt malloc run error, memory insufficient.");exit(EXIT_FAILURE);}n = vsnprintf(rt, sz, format, ap);if (n < sz)break;//重新开始,期待未来free(rt);sz <<= 1;}}else {rt = tmp;}cstring r = malloc(sizeof(struct cstring_data) + (n+1)*sizeof(char));if (NULL == r) {cerr("r malloc run error, memory insufficient.");exit(EXIT_FAILURE);}r->cstr = (char*)(r + 1);r->type = 0;r->ref = 1;r->hash = 0;memcpy(r->cstr, rt, n+1);// tmp != rt 时候, rt 构建临时区为 临时的if (tmp != rt) free(rt);//save tmp atomicif (!ATOM_CMP(__cache, NULL, tmp))free(tmp);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 == _INT_STRING_ONSTACK) {int n = vsnprintf(s->cstr, _INT_ONSTACK, format, ap);if (n >= _INT_ONSTACK) {s = __cstring_format(format, ap);sb->str = s;}elses->hash = n;}else {cstring_release(sb->str);s = __cstring_format(format, ap);sb->str = s;}va_end(ap);return s;
}

到这里基本结构就完成了. 简单说一下,当我写到下面这块

void 
cstring_free_persist(cstring s) //用完释放,这些api CSTRING_LITERAL宏中自动调用
{if (s->type == _INT_STRING_PERMANENT)free(s);
}cstring 
cstring_grab(cstring s)
{if (s->type & (_INT_STRING_PERMANENT | _INT_STRING_INTERNING))return s;if (s->type == _INT_STRING_ONSTACK)return __cstring_clone(s->cstr, s->hash);// 后面就是临时串 type == 0if (0 == s->ref) //没有引用让其变为持久串,不说内存泄露了,就说已经释放内存能不能用了都是问题s->type = _INT_STRING_PERMANENT;elseATOM_ADD_FETCH(s->ref, 1);return s;
}void 
cstring_release(cstring s)
{if (0 != s->type)return;if (0 == s->ref)return;ATOM_ADD_FETCH(s->ref, -1); //为了兼容 window特别处理if (s->ref == 0)free(s);
}

补充说明一下,这里  ATOM_ADD_FETCH 返回的是 %hu 的零, 但是 if ((hu)0 == -1)却不等,这是 数据格式默认变成LONG比较的结果.

所以先进行原子操作,再去处理数据. 属于一个隐含的知识点.

扩展一下, 当我们用VS2015 或者说Microsoft 系列IDE写C程序,都是伪C代码,走的是C++编译器的extern "C" 部分. 比较恶心.

对于VS DEBUG 模式下检测内存 的方式是, 在你申请内存时候额外添加空间,free时候回检测,这也就是他检测内存异常而定手段.

具体见

// Tests the array of size bytes starting at first.  Returns true if all of the
// bytes in the array have the given value; returns false otherwise.
static bool __cdecl check_bytes(unsigned char const* const first,unsigned char        const value,size_t               const size) throw()
{unsigned char const* const last{first + size};for (unsigned char const* it{first}; it != last; ++it){if (*it != value)return false;}return true;
}

这里再扩展一下,自己的多个IDE编程感受, 用gcc的时候你需要小心翼翼,明白很多细节,否则 直接跪了. 而用VS开发,很大方去你妈,不懂没关系

就是乱写,编译调试都不用太关心,省了1半开发调试时间.只高不低,生产力提升了.技术下降了.真希望Linux 上有个可视化的VS.

到这里扩展结束,继续说一下,它坑的地方

特别是对于 cstring_grab 中 0 == s->ref 的时候, 这时候 s 是一个被释放的临时串. 这样改个类型就直接返回了,相当于

使用已经释放的内存,多恐怖.

就是到这里, 感觉这个玩具已经扶不起来,例如

cstring_cat => cstring_release =>cstring_grab 这种程序崩了.如下

    // 测试内存混乱puts("\n--新的测试开始--\n");CSTRING_BUFFER(cu);cstring ks = cstring_cat(cu, "你好111111111111111111111111111111111111111111111111111111111111111111111111111111""好的11111111111111111111111111111111111111111111111111111111111111111111111111111111111""坑啊22222222222222222222222222222222222222222222222222222222222222222222222222222222222""你能力比我强,强改只会走火入魔,坑""1111111111111111111111111111111111112222222222222222222222222222222222222222222222222222222222");printf("type:%u, ref:%u, cstr:%s\n",ks->type, ks->ref, ks->cstr);CSTRING_CLOSE(cu);//这里继续使用这个串cstring bks = cstring_grab(ks); // 它也没有起死回生的能力,代码崩掉printf("type:%u, ref:%u, cstr:%s\n", bks->type, bks->ref, bks->cstr);

代码一执行,程序就崩了.

不想改了, 强改比自己能力强的 设计问题 容易引火焚身.

大家注意一下,有好想法, 可以试试,改好了分享. 我的感觉内存 管理方式隐含的太多了. 有点乱.绝逼内存泄露,毕竟让别人用.

4.运行实例

首先看原始的测试demo

test.c

#include "sc_string.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(void) 
{__test();#ifdef _MSC_VER system("pause");
#endif // !_MSC_VERreturn 0;
}

window 运行结果

到这里window 上基本都跑起来, 现在我们在gcc上测试一下. 首先需要将这些文件上传到Linux服务器上,上传之前统一用utf-8编码保存.

上面是Linux 跑的结果, 其中Makefile 文件内容如下

test.out : test.c sc_string.cgcc -g -Wall -march=native -o $@ $^

到这里 这个高级玩具要告一段落. 还有好多坑,这里就没说了. 例如 cstring_cat cstring_printf 这样分配太慢了, 搞一次不行又重头搞一次, 前面都是无用功.

但作为玩具已经够炫了.期待云风前辈重构成 实战级别的 c字符串, 反正我进过这次教训,觉得C中 char*,const char*, const char * const 够用了.

后记

  大家有机会可以去cloudwn  githup 上 下载 cstring-master 玩玩, 感受一下别人的代码习惯和风格和设计思路.

有机会下次分享 实战中的简单日志库. 欢迎吐槽,因为技术很菜总有不懂地方和错误的地方.

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

相关文章:

C# 获取当前路径方法

//获取包含清单的已加载文件的路径或 UNC 位置。 public static string sApplicationPath Assembly.GetExecutingAssembly ( ).Location; //result: X:\xxx\xxx\xxx.dll (.dll文件所在的目录.dll文件名) //获取当前进程的完整路径&#xff0c;包含文件名(进程名)。 string st…

土地档案管理系统需求分析

土地档案管理系统需求分析 1 项目背景 随着国土大面积调查工作的全面展开和城镇地籍管理工作得以日趋细化&#xff0c;各种野外调查数据&#xff0c;不同比例尺图件资料急剧增加。特别是城市建设的空前发展以及土地有偿使用法规的实施&#xff0c;使得地籍变更日益频繁、地籍信…

mysql8.0 服务移除_Linux下彻底删除Mysql 8.0服务的方法

观看本文前最好有一定的Linux命令基础&#xff0c;具体为centos7.3环境中清除使用yum安装的Mysql卸载前请先关闭Mysql服务service mysql stop使用 rpm 命令的方式查看已安装的mysqlrpm -qa|grep mysql开始卸载Mysql服务使用yum安装需要先进行清除服务等yum remove mysql mysql-…

老李推荐:第14章4节《MonkeyRunner源码剖析》 HierarchyViewer实现原理-装备ViewServer-端口转发 1...

老李推荐&#xff1a;第14章4节《MonkeyRunner源码剖析》 HierarchyViewer实现原理-装备ViewServer-端口转发 在初始化HierarchyViewer的实例过程中&#xff0c;HierarchyViewer会调用自己的成员方法setupViewServer来把ViewServer装备好&#xff0c;那么我们这里先看下这个方法…

泛型详解 高级进阶

泛型详解 高级进阶 转载于:https://www.cnblogs.com/thiaoqueen/p/10830499.html

vim 的中文编码问题

在vim编辑的时候会出现中文编码问题&#xff0c;我们可以这样解决在/usr/share/vim/的目录下有一个vimrc文件打开这个文件&#xff0c;你可能要用sudo打开然后在最后输入这行代码保存即可&#xff1a;$> sudo vim /usr/share/vim/vimrc set fileencodingsutf-8,gb2312,gbk,g…

mysql回表_到底什么情况下mysql innodb会发生回表操作?

谢邀MySQL innodb的主键索引是簇集索引&#xff0c;也就是索引的叶子节点存的是整个单条记录的所有字段值&#xff0c;不是主键索引的就是非簇集索引&#xff0c;非簇集索引的叶子节点存的是主键字段的值。回表是什么意思&#xff1f;就是你执行一条sql语句&#xff0c;需要从两…

经验分享:CSS浮动(float,clear)通俗讲解

很早以前就接触过CSS&#xff0c;但对于浮动始终非常迷惑&#xff0c;可能是自身理解能力差&#xff0c;也可能是没能遇到一篇通俗的教程。 前些天小菜终于搞懂了浮动的基本原理&#xff0c;迫不及待的分享给大家。 写在前面的话&#xff1a; 由于CSS内容比较多&#xff0c;小菜…

前端开发学习Day27

第27天&#xff0c;我只做了一个案例。布局部分花了一整个上午&#xff0c;很乱。代码还是写的少&#xff0c;没有思路。下午好不容易做好了布局&#xff0c;写脚本的时候又被卡死&#xff0c;我现在严重怀疑自己的大脑是怎么长的……本着不抛弃不放弃的原则&#xff0c;晚上找…

对象模型创建SharePoint2010多选字段SPFieldMultiChoice

在使用页面方式创建SharePoint 2010的选项(Choice)字段时&#xff0c;选项字段的显示方式有3种&#xff1a;下拉列表、单选按钮、多选。但是如果使用对象模型创建时&#xff0c;下拉列表和单选按钮只能使用SPFieldChoice类来创建&#xff0c;而多选显示方式就要使用SPFieldMult…

docker mysql忘记密码_docker基于mysql镜像构建mysql容器忘记密码解决办法

环境&#xff1a;[rootmaster-106 ~]# cat /etc/redhat-releaseCentOS Linux release 7.6.1810 (Core)[rootmaster-106 ~]# docker --versionDocker version 19.03.13, build 4484c46d9dMySQL 5.7.31# 进入mysql容器[rootmaster-106 ~]# docker ps|grep mysql05759803adb9 mysq…

Android App优化之延长电池续航时间

禁用广播接收器 确保广播接收器在真正须要时才运行指令&#xff0c;在onResume中当中广播接收器&#xff0c;在onPause中禁用。 在manifest文件里声明广播接收器时&#xff0c;事先默认配置成禁用的 <receiver android:name".BatterReceiver" android:enabled&quo…

myeclipse中安装svn插件

1、下载最新的SVN包(我下的是1.0.6版)&#xff1a; http://subclipse.tigris.org/servlets/ProjectDocumentList?folderID2240 2、在你的磁盘上任意位置创建文件夹&#xff1a;“myplugins/svn”。名字可以任取&#xff0c;为了方便插件管理&#xff0c;建议名称为“myplugins…

字节码学院全栈学习笔记

今天正式加入字节码学院&#xff0c;努力学习Java全栈&#xff0c;争取在6个月内称为一个了解软件行业的人&#xff0c;本人在这里立铁为证&#xff1a; 搭建vue 组件化开发环境时&#xff0c;需要安装node.js step 01: nodejs 安装 sudo apt updatesudo apt install nodejsno…

mysql 索引 二叉树_MySQL 的索引,为什么是 B+而不是平衡二叉树

数据库为什么使用 B 树&#xff1f;前言讲到索引&#xff0c;第一反应肯定是能提高查询效率。例如书的目录&#xff0c;想要查找某一章节&#xff0c;会先从目录中定位。如果没有目录&#xff0c;那么就需要将所有内容都看一遍才能找到。索引的设计对程序的性能至关重要&#x…

Spring Boot 2 快速教程:WebFlux 快速入门(二)

2019独角兽企业重金招聘Python工程师标准>>> 摘要: 原创出处 https://www.bysocket.com 「公众号&#xff1a;泥瓦匠BYSocket 」欢迎关注和转载&#xff0c;保留摘要&#xff0c;谢谢&#xff01; 02&#xff1a;WebFlux 快速入门实践 文章工程&#xff1a; JDK 1.8…

关于捕获键盘信息的processDialogkey方法

在一些控件里的keydown方法&#xff0c;没有办法捕获所有的按键消息 比如自己写一个窗体控件库&#xff0c;继承了UserControl 但是没有办法捕获一些键&#xff0c;比如方向键等 所以必须重载 processDialogkey 方法 processDialogkey 的描述 在msdn中是这样的 在消息预处理过程…

Visual Studio进行Web性能测试- Part III

原文作者&#xff1a;Ambily.raj 对于一个多用户的应用程序&#xff0c;性能是非常重要的。性能不仅是执行的速度&#xff0c;它包括负载和并发方面。Visual Studio是可以用于性能测试的工具之一。Visual Studio Test版或Visual Studio 2010旗舰版为自动化测试提供了支持。 介绍…

php mysql source_Mysql数据库导入命令Source详解

Mysql数据库导入命令Source详解几个常用用例&#xff1a;1.导出整个数据库mysqldump -u 用户名 -p 数据库名 > 导出的文件名mysqldump -u root -p dataname >dataname.sql这个时候会提示要你输入root用户名的密码,输入密码后dataname数据库就成功备份在mysql/bin/目录中.…

课堂练习(续)

源程序&#xff1a; Text_2.java import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.io.InputStreamReader; import java.te…

任务二:1、选择器 2、连接集中状态的顺序 3、浮动的用发和原理 4、盒模型在IE和Google等不同浏览器的区别与联系...

1、选择器类型 标签选择器&#xff0c;类选择器&#xff0c;ID选择器&#xff0c;组合选择器&#xff0c;伪类和伪元素&#xff0c;属性选择器&#xff0c;子选择器&#xff0c;同胞选择器&#xff0c; :not()选择器 1、同胞选择器 相邻同胞标签&#xff1a; h2 p{ color:red…

struct发送与显示中文

中文的编码显示常常是一个让人头痛的问题&#xff0c;网络传输的时候中文也会变成二进制的流&#xff0c;接收方显示就成了一个大问题。 今天使用python的struct模块来对数据封包解包&#xff0c;同样有这个问题。解决方法是&#xff1a;一般会把python代码文件声明为utf-8编码…

mysql5.7.22密码设置_mysql5.7.22版本修改root密码

mysql5.6之前修改密码(字段password)mysql> use mysql;mysql> update user set passwordpassword(‘123‘) where user‘root‘ and host‘localhost‘;mysql> flush privileges;mysql 5.7.22版本修改密码(字段是authentication_string)mysql> use mysql;mysql>…

配置 php-fpm 监听的socket

一般现在我们配置的PHP的web环境&#xff0c;如LNMP&#xff08;linuxNginxMysqlPHP&#xff09;, 这里linux可能是centos, ubuntu..., 数据库可能是mysql, postgresql, sql server等。。 在服务器上安装PHP-FPM&#xff0c; nginx后&#xff0c; 我们要配置Nginx的http模块&am…

laravel基础课程---8、laravel响应和视图(响应是什么)

laravel基础课程---8、laravel响应和视图&#xff08;响应是什么&#xff09; 一、总结 一句话总结&#xff1a; 就是向请求返回的响应数据&#xff08;一般为html&#xff08;视图&#xff09;&#xff0c;当然也可以是变量值&#xff09;&#xff1a;所有的路由及控制器必须返…

ios应用内购买

参考&#xff1a; 1、http://troybrant.net/blog/2010/01/in-app-purchases-a-full-walkthrough/ 2、http://iosdeveloper.diandian.com/post/2011-08-26/4366441转载于:https://www.cnblogs.com/foxmin/archive/2012/09/08/2676580.html

mysql查询解析过程_MySQL查询执行过程详解

查询是用户通过设置某些查询条件&#xff0c;从表或其他查询中选取全部或者部分数据&#xff0c;以表的形式显示数据供用户浏览。查询是一个独立的、功能强大的、具有计算功能和条件检索功能的数据库对象。MySQL数据库中&#xff0c;MySQL查询同样是数据库的核心操作&#xff0…

.net erp(办公oa)开发平台架构之流程服务概要介绍

背景 搭建一个适合公司erp业务的开发平台。 架构概要图&#xff1a; 流程引擎开发平台&#xff1a; 包含流程引擎设计器&#xff0c;流程管理平台&#xff0c;流程引擎服务。目前只使用单个数据库进行管理。 流程引擎设计器 采用silverlight进行开发&#xff0c;本质是对流程…

数据分析-pca协方差

协方差是反映的变量之间的二阶统计特性&#xff0c;如果随机向量的不同分量之间的相关性很小&#xff0c;则所得的协方差矩阵几乎是一个对角矩阵。转载于:https://www.cnblogs.com/erweiyang/archive/2012/09/08/2676997.html

在javascript中判断类型

String 一个字符串始终是一个字符串&#xff0c;所以这一块是很容易。除非使用new&#xff08;new String&#xff09;调用&#xff0c;否则typeof将返回“object”。所以也要包含那些可以使用的字符串instanceof。 // Returns if a value is a string function isString (valu…