介绍
sds 简称 simple dynamic string,意思就是简单的动态字符串,类似于 C++中的 string,可以动态调整长度。
定义
他的定义非常简单
typedef char *sds;
/* 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[];
};
这里sdshdr5、sdshdr8 等 hdr使用不同类型的 int 定义的长度和内存大小,目的也是节省一些内存。
__attribute__ ((__packed__)) 是 gcc 编译器扩展,他告诉编译器取消结构体内的默认内存对齐优化,按照每个成员大小实际进行紧凑排列,节省内存空间。
char buf[]是 sds 实际上的 data,它是一个柔性数组(flexible array member),C99 标准引入的功能,柔性数组在结构体中不占用任何空间,只作为一个符号存在,但是结构体和数组在内存中是连续存储的,只需一次内存分配。
可以看出 sds 是一种紧凑节省内存,连续的结构,他将字符串的属性和存储数据放在同一块连续的内存中。 有两大优点:
-
方便转换不同类型的 sds
-
cache 优化,在 CPU 会连续读取内存,sds 的结构中 sdshdr 和 data 是连续的,可以降低 cache miss
创建
可以通过 sdsnewlen 函数创建 sds,该函数调用了_sdsnewlen
/* Create a new sds string with the content specified by the 'init' pointer
* and 'initlen'.
* If NULL is used for 'init' the string is initialized with zero bytes.
* If SDS_NOINIT is used, the buffer is left uninitialized;
*
* The string is always null-terminated (all the sds strings are, always) so
* even if you create an sds string with:
*
* mystring = sdsnewlen("abc",3);
*
* You can print the string with printf() as there is an implicit \0 at the
* end of the string. However the string is binary safe and can contain
* \0 characters in the middle, as the length is stored in the sds header. */
sds _sdsnewlen(const void *init, size_t initlen, int trymalloc) {
void *sh;
char type = sdsReqType(initlen);
/* Empty strings are usually created in order to append. Use type 8
* since type 5 is not good at this. */
if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;
int hdrlen = sdsHdrSize(type);
size_t bufsize;
assert(initlen + hdrlen + 1 > initlen); /* Catch size_t overflow */
sh = trymalloc?
s_trymalloc_usable(hdrlen+initlen+1, &bufsize) :
s_malloc_usable(hdrlen+initlen+1, &bufsize);
if (sh == NULL) return NULL;
adjustTypeIfNeeded(&type, &hdrlen, bufsize);
return sdsnewplacement(sh, bufsize, type, init, initlen);
}
_sdsnewlen 计算了 hdr 的 size,之后申请了hdrlen+initlen+1的内存,这里多申请的1 字节是为了字符串结尾的\0。
/* Initializes an SDS within pre-allocated buffer. Like, placement new in C++.
*
* Parameters:
* - `buf` : A pre-allocated buffer for the SDS.
* - `bufsize`: Total size of the buffer (>= `sdsReqSize(initlen, type)`). Can use
* a larger `bufsize` than required, but usable size won't be greater
* than `sdsTypeMaxSize(type)`.
* - `type` : The SDS type. Can assist `sdsReqType(length)` to compute the type.
* - `init` : Initial string to copy, or `SDS_NOINIT` to skip initialization.
* - `initlen`: Length of the initial string.
*
* Returns:
* - A pointer to the SDS inside `buf`.
*/
sds sdsnewplacement(char *buf, size_t bufsize, char type, const char *init, size_t initlen) {
assert(bufsize >= sdsReqSize(initlen, type));
int hdrlen = sdsHdrSize(type);
size_t usable = bufsize - hdrlen - 1;
sds s = buf + hdrlen;
unsigned char *fp = ((unsigned char *)s) - 1; /* flags pointer. */
switch(type) {
case SDS_TYPE_5: {
*fp = type | (initlen << SDS_TYPE_BITS);
break;
}
case SDS_TYPE_8: {
SDS_HDR_VAR(8,s);
sh->len = initlen;
debugAssert(usable <= sdsTypeMaxSize(type));
sh->alloc = usable;
*fp = type;
break;
}
case SDS_TYPE_16: {
SDS_HDR_VAR(16,s);
sh->len = initlen;
debugAssert(usable <= sdsTypeMaxSize(type));
sh->alloc = usable;
*fp = type;
break;
}
case SDS_TYPE_32: {
SDS_HDR_VAR(32,s);
sh->len = initlen;
debugAssert(usable <= sdsTypeMaxSize(type));
sh->alloc = usable;
*fp = type;
break;
}
case SDS_TYPE_64: {
SDS_HDR_VAR(64,s);
sh->len = initlen;
debugAssert(usable <= sdsTypeMaxSize(type));
sh->alloc = usable;
*fp = type;
break;
}
}
if (init == SDS_NOINIT)
init = NULL;
else if (!init)
memset(s, 0, initlen);
else if (initlen)
memcpy(s, init, initlen);
s[initlen] = '\0';
return s;
}
最后调用的sdsnewplacement函数中赋值了 flag、alloac 和 flag,最后确保字符串安全性,在结尾处加上\0
销毁
/* Free an sds string. No operation is performed if 's' is NULL. */
void sdsfree(sds s) {
if (s == NULL) return;
s_free((char*)s-sdsHdrSize(s[-1]));
}
利用柔性数组特点,通过(char*)s-sdsHdrSize(s[-1]) 拿到 sds 连续内存的起始位置,进行释放。连续内存的方式可以通过一次释放释放所有内存。