背景介绍
为什么大多数人选择c++而不是c?我想可能是因为c标准里面没有方便使用的字符串"string"。虽然c标准确实不应该规定字符串实现,但是作为最终使用者的我们,还是迫切希望能有一份开箱即用,并且拥有现代品质的字符串。避免重造轮子的同时也能满足日常需要。
基本思路
// clang generally mode simplify string.
// https://github.com/llvm-mirror/libcxx/blob/master/include/string
// https://joellaity.com/2020/01/31/string.html
1.最终使用案例
接口参照std::string,以下为节选测试用例。
我拷贝替换增加了Utf16String,Utf32String,它们裁剪掉了string的printf部分。
https://bitbucket.org/mm_longcheng/mm-core/src/dev/mm/src/core/mmString.h
// 编译构建常量字符串,存储在全局区,没有堆分配
const struct mmString c = mmString_Make("abc");
const char* cs = mmString_Data(&c);
size_t cn = mmString_Size(&c);
printf("cs: %s %d\n", cs, (int)cn);
// 动态构建常量字符串,存储在局部区,没有堆分配
struct mmString w;
mmString_MakeWeak(&w, "123");
// 能正常重置,便于重新定义成可能的动态串
mmString_Reset(&w);
// 动态构建普通字符串,存储在局部区,可能堆分配
// x64位 长度超过22就会触发堆分配
struct mmString s;
mmString_Init(&s);
mmString_Assigns(&s, "456");
mmString_Destroy(&s);
2.前置数据结构组件
// 我们需要大小端判定宏
#define MM_ENDIAN <平台相关>
#define MM_ENDIAN_LITTLE 1
#define MM_ENDIAN_BIGGER 2
// 我们需要内存接口,简单重定义为c标准接口即可
// 重定义是为了预留可供替换的备选内存管理
#define mmMalloc
#define mmRealloc
#define mmFree
#define mmMemset(buf, c, n)
#define mmMemcpy(dst, src, n)
#define mmMemmove(dst, src, n)
#define mmMovemem(dst, src, n)
#define mmMemcmp(s1, s2, n)
3.主要数据结构
typedef char mmUtf8_t;
struct mmStringLong
{
// capacity
size_t capacity;
// size
size_t size;
// data pointer
char* data;
};
#if (MM_ENDIAN == MM_ENDIAN_BIGGER)
// bigger endian
#define mmStringTinyMaskMacro 0x80
#define mmStringLongMaskMacro ~((size_t)(~((size_t)(0))) >> 1)
#else
// little endian
#define mmStringTinyMaskMacro 0x01
#define mmStringLongMaskMacro 0x1ul
#endif
enum
{
mmStringLongCapacity = (sizeof(struct mmStringLong) - 1) / sizeof(char),
mmStringTinyCapacity = mmStringLongCapacity > 2 ? mmStringLongCapacity : 2,
};
struct mmStringTiny
{
union
{
// size
unsigned char size;
// lx
char lx;
};
// data buffer
char data[mmStringTinyCapacity];
};
union mmStringUlxx { struct mmStringLong __lxx; struct mmStringTiny __txx; };
enum { mmStringNWords = sizeof(union mmStringUlxx) / sizeof(size_t) };
struct mmStringRaws
{
// raw words buffer
size_t words[mmStringNWords];
};
struct mmString
{
union
{
// long
struct mmStringLong l;
// tiny
struct mmStringTiny s;
// raws
struct mmStringRaws r;
};
};
4.主要处理函数
// 是否为长串
static mmInline mmBool_t mmString_IsLong(const struct mmString* p)
{
return p->s.size & mmStringTinyMask;
}
// 这里仅截取小端的情况
// little endian
static mmInline void mmString_SetTinySize(struct mmString* p, size_t size)
{
p->s.size = (unsigned char)(size << 1);
}
static mmInline size_t mmString_GetTinySize(const struct mmString* p)
{
return p->s.size >> 1;
}
static mmInline void mmString_SetLongSize(struct mmString* p, size_t size)
{
p->l.size = size;
}
static mmInline size_t mmString_GetLongSize(const struct mmString* p)
{
return p->l.size;
}
// 构建弱串时,我们将其定义为长串,并且其容量为0
MM_EXPORT_DLL void mmString_MakeWeaksn(struct mmString* p,
const char* __s, size_t __n)
{
// const reference long capacity is 0.
p->l.capacity = mmStringLongMask;
p->l.size = __n;
p->l.data = (char*)__s;
}
// 我们需要支持弱分配的串,删除处理时需要将其排除
static void mmString_Deallocate(struct mmString* p)
{
size_t __cap = mmString_GetLongCapacity(p);
if (mmString_IsLong(p) && 0 != __cap)
{
// long mode and long capacity equal 0 is weak or const reference.
char* __p = mmString_GetLongPointer(p);
// __alloc_traits::deallocate(__alloc(), __p, __cap);
mmUtf8_Deallocate(__p, __cap);
}
}
5.原理
借用两张图,感谢llvm组织设计出了这么精巧的字符串
a.使用头部一位来划定长串还是短串,x64下字符串最长为2的63次幂-1,而不是64
b.长串会分配两份内存,一份在栈上,格式为data指向堆块
c.短串会复用栈上的数据块,一字节存储短串长度,23字节为存储,短串最长22字节