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

對雲風 cstring 第二次解析,雲風cstring

編輯:關於C語言

對雲風 cstring 第二次解析,雲風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 pool
        si->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 == 0
    if (0 == s->ref) //沒有引用讓其變為持久串,不說內存洩露了,就說已經釋放內存能不能用了都是問題
        s->type = _INT_STRING_PERMANENT;
    else
        ATOM_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 atomic
    if (tmp) {
        //tmp 獲取 __cache值, 如果 __cache == tmp ,會讓 __cache = NULL
        tmp = 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 atomic
    if (!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;
        }
        else
            s->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 == 0
    if (0 == s->ref) //沒有引用讓其變為持久串,不說內存洩露了,就說已經釋放內存能不能用了都是問題
        s->type = _INT_STRING_PERMANENT;
    else
        ATOM_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_VER

    return 0;
}

window 運行結果

 

到這裡window 上基本都跑起來, 現在我們在gcc上測試一下. 首先需要將這些文件上傳到Linux服務器上,上傳之前統一用utf-8編碼保存.

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

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

到這裡 這個高級玩具要告一段落. 還有好多坑,這裡就沒說了. 例如 cstring_cat cstring_printf 這樣分配太慢了, 搞一次不行又重頭搞一次, 前面都是無用功.

但作為玩具已經夠炫了.期待雲風前輩重構成 實戰級別的 c字符串, 反正我進過這次教訓,覺得C中 char*,const char*, const char * const 夠用了.

 

後記

  大家有機會可以去cloudwn  githup 上 下載 cstring-master 玩玩, 感受一下別人的代碼習慣和風格和設計思路.

有機會下次分享 實戰中的簡單日志庫. 歡迎吐槽,因為技術很菜總有不懂地方和錯誤的地方.

 

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