SDS(Simple Dynamic String)定义 源码中定义 :
总结
sds类型实际为 char * 的别名,这样做的好处是,可以复用很多C的原生函数库。
SDS的结构体定义sdshdr sdshdr有好五个
类别,它们分别是:sdshdr5
,sdshdr8
,sdshdr16
,sdshdr32
,sdshdr64
,其中sdshdr5是不使用的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 struct __attribute__ ((__packed__ )) sdshdr5 { unsigned char flags; char buf[]; };struct __attribute__ ((__packed__ )) sdshdr8 { uint8_t len; uint8_t alloc; unsigned char flags; char buf[]; };struct __attribute__ ((__packed__ )) sdshdr16 { uint16_t len; uint16_t alloc; unsigned char flags; char buf[]; };struct __attribute__ ((__packed__ )) sdshdr32 { uint32_t len; uint32_t alloc; unsigned char flags; char buf[]; };struct __attribute__ ((__packed__ )) sdshdr64 { uint64_t len; uint64_t alloc; unsigned char flags; char buf[]; };
结构体说明
len
: 字符串的长度
alloc
: 字符串的容量
flags
: 用低三位表示header类型,高五位未使用,为何是低三位表示,见下方
buf[]
: 实际存储的字符串
细节说明 attribute ((packed)) 说明__attribute__ ((packed)) 说明
attribute ((packed))实际作用是 取消编译阶段的内存优化对齐功能。
例如:struct aa {char a; int b;}; sizeof(aa) == 8;但是struct attribute ((packed)) aa {char a; int b;}; sizeof(aa) == 5;
这个很重要,redis源码中不是直接对sdshdr某一个类型操作,往往参数都是sds,而sds就是结构体中的buf,在后面的源码分析中,你可能会经常看见s[-1]
这种魔法一般的操作,而按照sdshdr内存分布s[-1]就是sdshdr中flags变量,由此可以获取到该sds指向的字符串的类型。
常量定义 1 2 3 4 5 6 7 #define SDS_TYPE_5 0 #define SDS_TYPE_8 1 #define SDS_TYPE_16 2 #define SDS_TYPE_32 3 #define SDS_TYPE_64 4 #define SDS_TYPE_MASK 7 #define SDS_TYPE_BITS 3
为何是低三位表示类型
SDS_TYPE只占用了0,1,2,3,4
五个数字,正好占用三位,我们就可以使用flags&SDS_TYPE_MASK
来获取动态字符串对应的字符串类型
SDS中的宏定义函数 根据指向buf的sds变量s,得到sdshdr对应的指针变量 1 #define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (void*)((s)-(sizeof(struct sdshdr##T)));
根据指向buf的sds变量s,得到sdshdr对应的指针地址 1 #define SDS_HDR(T,s) ((struct sdshdr##T \*)((s)-(sizeof(struct sdshdr##T))));
获取sdshdr5字符串类型的长度 1 #define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS)
c中的##语法
##
是c语言中的连接符, 前加 ## 或者后加 ## 将标记作为一个合法的标识符的一部分,不是字符串.多用于多行的宏定义中
。+
本宏声明中,SDS_HDR_VAR(8,s) 相当于 struct sdshdr8 *sh = (void*)((s)-(sizeof(struct sdshdr8)));
SDS相关内联函数 获取字符串长度 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 static inline size_t sdslen (const sds s) { unsigned char flags = s[-1 ]; switch (flags&SDS_TYPE_MASK) { case SDS_TYPE_5: return SDS_TYPE_5_LEN(flags); case SDS_TYPE_8: return SDS_HDR(8 ,s)->len; case SDS_TYPE_16: return SDS_HDR(16 ,s)->len; case SDS_TYPE_32: return SDS_HDR(32 ,s)->len; case SDS_TYPE_64: return SDS_HDR(64 ,s)->len; } return 0 ; }
知识点
该处就使用到了 取消编译阶段的内存优化对齐功能
,直接使用s[-1]获取到flags成员的值,然后根据flags&&SDS_TYPE_MASK来获取到动态字符串对应的类型进而 获取动态字符串的长度
,至于 灵性的 s[-1]操作,没研究明白,后续补充
获取字符串可用空间 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 static inline size_t sdsavail (const sds s) { unsigned char flags = s[-1 ]; switch (flags&SDS_TYPE_MASK) { case SDS_TYPE_5: { return 0 ; } case SDS_TYPE_8: { SDS_HDR_VAR(8 ,s); return sh->alloc - sh->len; } case SDS_TYPE_16: { SDS_HDR_VAR(16 ,s); return sh->alloc - sh->len; } case SDS_TYPE_32: { SDS_HDR_VAR(32 ,s); return sh->alloc - sh->len; } case SDS_TYPE_64: { SDS_HDR_VAR(64 ,s); return sh->alloc - sh->len; } } return 0 ; }
知识点
获取动态字符串可使用的空间,从这里可以看出来,SDS和我平常所用到的C语言的原生字符串有差别,因为从获取可用空间的计算方法来看,并未考虑到字符串需要以\0结尾
,因为 结构体本身带有长度的成员len
,不需要\0来做字符串结尾的判定,而且不使用\0作为结尾有很多好处,可以存储的类型多样性就提高了
设置sds的长度 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 static inline void sdssetlen (sds s, size_t newlen) { unsigned char flags = s[-1 ]; switch (flags&SDS_TYPE_MASK) { case SDS_TYPE_5: { unsigned char *fp = ((unsigned char *)s)-1 ; *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS); } break ; case SDS_TYPE_8: SDS_HDR(8 ,s)->len = newlen; break ; case SDS_TYPE_16: SDS_HDR(16 ,s)->len = newlen; break ; case SDS_TYPE_32: SDS_HDR(32 ,s)->len = newlen; break ; case SDS_TYPE_64: SDS_HDR(64 ,s)->len = newlen; break ; } }
增加sds的长度 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 static inline void sdsinclen (sds s, size_t inc) { unsigned char flags = s[-1 ]; switch (flags&SDS_TYPE_MASK) { case SDS_TYPE_5: { unsigned char *fp = ((unsigned char *)s)-1 ; unsigned char newlen = SDS_TYPE_5_LEN(flags)+inc; *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS); } break ; case SDS_TYPE_8: SDS_HDR(8 ,s)->len += inc; break ; case SDS_TYPE_16: SDS_HDR(16 ,s)->len += inc; break ; case SDS_TYPE_32: SDS_HDR(32 ,s)->len += inc; break ; case SDS_TYPE_64: SDS_HDR(64 ,s)->len += inc; break ; } }
获取sds已分配空间(容量)的大小 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 static inline size_t sdsalloc (const sds s) { unsigned char flags = s[-1 ]; switch (flags&SDS_TYPE_MASK) { case SDS_TYPE_5: return SDS_TYPE_5_LEN(flags); case SDS_TYPE_8: return SDS_HDR(8 ,s)->alloc; case SDS_TYPE_16: return SDS_HDR(16 ,s)->alloc; case SDS_TYPE_32: return SDS_HDR(32 ,s)->alloc; case SDS_TYPE_64: return SDS_HDR(64 ,s)->alloc; } return 0 ; }
设置sds已分配空间(容量)的大小 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 static inline void sdssetalloc (sds s, size_t newlen) { unsigned char flags = s[-1 ]; switch (flags&SDS_TYPE_MASK) { case SDS_TYPE_5: break ; case SDS_TYPE_8: SDS_HDR(8 ,s)->alloc = newlen; break ; case SDS_TYPE_16: SDS_HDR(16 ,s)->alloc = newlen; break ; case SDS_TYPE_32: SDS_HDR(32 ,s)->alloc = newlen; break ; case SDS_TYPE_64: SDS_HDR(64 ,s)->alloc = newlen; break ; } }
C字符串与SDS对比
C字符串
SDS
获取字符串长度时间复杂度 O(N)
获取字符串长度时间复杂度 O(1)
API不安全,可能造成缓冲区溢出
API是安全的,不会造成缓冲区溢出
修改字符串长度N次,必然要执行 N 次内存分配
修改字符串长度N次,至多执行 N 次内存分配
只能保存文本数据
可以保存文本或二进制数据
可使用所有 <string.h>库中的函数
可使用部分 <string.h>库中的函数
参考资料