C基础 带你手写 redis sds
前言 - Simple Dynamic Strings
antirez 想统一 Redis,Disque,Hiredis 项目中 SDS 代码, 因此构建了这个项目
https://github.com/antirez/sds . 更多介绍的背景知识, 可以阅读 README.md.
sds 项目是 C 字符串数据结构在实际环境中一种取舍实现, 库本身是非线程安全的. 下面
来带大家手写相关代码, 透彻了解这个库的意图(antirez 注释的很棒).
#define SDS_MAX_PREALLOC (1024*1024)
/* Note: sdshdr5 is never used, we just access the flags byte directly.
* However is here to document the layout of type 5 SDS strings. */
struct __attribute__ ((__packed__)) sdshdr5 {
unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {
uint8_t len; /* used */
uint8_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr16 {
uint16_t len; /* used */
uint16_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
uint32_t len; /* used */
uint32_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
uint64_t len; /* used */
uint64_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
#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
#define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS)
#define SDS_HDR(T, s) ((struct sdshdr##T *)((s) - (sizeof(struct sdshdr##T))))
#define SDS_HDR_VAR(T, s) struct sdshdr##T * sh = SDS_HDR(T, s)
可以先分析 sdshdr5 sdshdr8 sdshdr16 sdshdr32 sdshdr64, 感受作者的意图.
首先这几个基本结构, 都有 len, alloc, flags, buf 4 个字段, 是不是. 有的同学会问, sdshdr5
中没有 alloc 和 len 字段呀, 这个啊 sdshdr5 结构比较特殊, 其 alloc 和 len 都隐含在 flags
中, 三者复用一个字段. 可以从函数宏 SDS_TYPE_5_LEN(f), 可以看出来. 因而 sdshdr5 表达的字符
串长度和字符串容量默认相同. 再考究一下 __attribute__ ((__packed__)) 意图(告诉编译器取消结
构在编译过程中的优化对齐, 按照实际占用字节数进行对齐). 对于取消结构内存编译对齐优化, 我的考究有
两点, 1 节约内存, 2 内存可移植变强.
随后多数是流水线代码, 非常好理解. 例如有段这样关系的代码 sdsalloc() = sdsavail() + sdslen()
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;
}
inline size_t sdsavail(const sds s) {
unsigned char flags = s[-1];
switch (flags & SDS_TYPE_MASK) {
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;
}
default:
return 0;
}
}
/* sdsalloc() = sdsavail() + sdslen() */
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;
}
是不是一下就理解了 sdsalloc(), sdsavail(), sdslen() 是干什么的呢 ❤
正文 - 代码抽样讲解
1. 重复代码可以修的更好
/* Helper for sdscatlonglong() doing the actual number -> string
* conversion. "s" must point to a string with room for at least
* SDS_LLSTR_SIZE bytes.
*
* The function returns the length of the null-terminated string
* representation stored at "s". */
#define SDS_LLSTR_SIZE 21
int sdsll2str(char *s, long long value) {
char *p, aux;
unsigned long long v;
size_t l;
/* Generate the string representation, this method produces
* an reversed string. */
v = (value < 0) ? -value : value;
p = s;
do {
*p++ = "0"+(v%10);
v /= 10;
} while(v);
if (value < 0) *p++ = "-";
/* Compute length and add null term. */
l = p-s;
*p = "