“真实”的 sdshdr 结构

在 《Redis 设计与实现》中,提到 sds 的实现结构 sdshdr 是这样的:

struct sdshdr {
    // 记录buf数组已使用字节的数量
    // 等于SDS所保存字符串的长度
    int len;
    
    // 记录buf数组中未使用的字节数
    int free;
    
    // 字节数组,用于保存字符串
    char buf[];
};

这可能是 Redis 以前的版本是这样的,笔者查看的源码是 7.0

在 Redis 7.0 中,sdshdr (在 sds.h 中)结构是这样的:

/* 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[];
};

根据字符串长度的不同,用来存放它的 sdshdr 类型也是不同的

在这段代码的上方有一段注释,意思是sdshdr5 结构是不使用的(网上有人说是使用的,具体可以看【Redis源码分析】一个对SDSHDR5是否使用的疑问 - SegmentFault 思否)。

接下来我们分析除 sdshdr5 之外,其他结构中的各个成员:

len: 已使用的字符串长度;
alloc: 为字符串分配的空间总长度;
flags: 标记当前结构的类型,即当前结构体是 8/16/32/64 位;
flags 只使用低三位字节,高五位是不使用的。三位足够表示5个数字,不同的值代表不同的类型,下面是关于不同类型的常量定义:
#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
    
buf: 指向字符串;

现在我们可以观察到 《Redis 设计与实现》中的 sdshdr 与 7.0 的 sdshdr 的区别,多了一个 flag 标识,且并没有 free 属性,而是 alloc 属性,而 alloc - len 即代表 free 的值。

在定义 sdshdr 结构时,我们发现前面加上了 __attribute__ ((__packed__)) ,它的作用是取消编译器的对齐,即结构 内的成员在内存中是紧凑的。

为什么不内存对齐呢?因为省一点点内存吗?

我们先看对齐后 SDS_TYPE_8SDS_TYPE_16SDS_TYPE_32SDS_TYPE_64 的内存布局是怎样的:

image-20220508193446143

可以看到不同类型的 sdshdr 对齐的字节数不同,这就让 (char*)buf - 1 无法让每种 sdshdr 都定位到 flags 的地址,如果想通过 buf 定位到 flags 的地址,需要进行类型判断,并且不同的系统可能有不同的对齐方式。

那为了这个就舍弃内存对齐从而降低效率吗?

在一篇文章中解释了 Redis 的另外一种内存对齐((3条消息) redis源码解读(一):基础数据结构之SDS_czrzchao的博客-CSDN博客):

redis 通过自己在malloc等c语言内存分配函数上封装了一层zmalloc,将内存分配收敛,并解决了内存对齐的问题。在内存分配前有这么一段代码:

if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1)); \    // 确保内存对齐!

这段代码写的比较抽象,简而言之就是先判断当前要分配的_n个内存是否是long类型的整数倍,如果不是就在_n的基础上加上内存大小差值,从而达到了内存对齐的保证。

虽然设计了 sdshdr 这几种结构,但实际在使用 sds 时,我们都是使用 sds 的接口来实现对 sds 的修改,并没有直接使用到这些结构。

通过 sds 获取其结构地址

在 sds.h 中,有这样两个宏:

#define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (void*)((s)-(sizeof(struct sdshdr##T)));

#define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))))

关于 sdshdr##T 中的 ## 我们就不详细解释了,在这里理解成它将 sdshdrT 连接在一起,即表示不同的 sdshdr 类型

关于 SDS_HDR_VAR(T,s), 参数 s 是sdshdr 结构体中的字符串指针,即等价于 buf,参数 T 则是表示不同类型的 sdshdr,取值可以为 8/16/32/64。然后看其实现,struct sdshdr##T *sh 是宏定义的一个变量, void* 是将结果转换为void* 类型,以便 sh 接收。而 (s) - ( sizeof(struct sdshdr##T) )则表示指向结构体变量的地址。首先sizeof(struct sdshdr##T) 计算的大小不包含 buf (柔性数组特点),而 s 的指向的地址就跟在 sdshdr 之后(在接下来的 _sdsnewlen 函数中会实现),我们看图示:

image-20220508111538993

注意,是 (s) - ( sizeof(struct sdshdr##T) ),s 的值是所存放字符串的起始地址,而不是 s 的地址。因此,该表达式得到的是结构体变量 sdshdr##T 的起始地址。

关于变量 struct sdshdr##T *sh 大家可能会有疑惑,其实就是相当于在调用宏的地方定义了一个 sh 变量。我们用一个例子模拟一下:

#include <stdio.h>

#define INTPTR_VAR int* p = NULL;

int main()
{
    int a = 10;
    INTPTR_VAR;
    p = &a;

    printf("%d\n", *p);

    return 0;
}

结果:
输出 10

根据宏的特点,在预处理时,宏就直接被表达式替换了,上述例子就等价于:

#include <stdio.h>

int main()
{
    int a = 10;
    int* p = NULL; // INTPTR_VAR
    p = &a;

    printf("%d\n", *p);

    return 0;
}

至此,我们就明白了 SDS_HDR_VAR 是定义一个指向保存字符串 s 的结构体指针

这样我们就好理解 SDS_HDR(T, S) 的作用了,即返回一个 保存字符串 s 的结构体地址

注意两者的差别:SDS_HDR_VAR 是定义变量保存结构体地址,SDS_HDR 是返回结构体地址。

关于sds属性的函数(sds.h)

sdslen

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;
}

顾名思义,该函数是用来获取字符串长度的。

参数 sds

typedef char * sds;

这里我们就知道了上面讲的 SDS_HDR 的作用了,通过字符串指针 s 来获取包含它的结构体地址,进一步访问其中的成员。
s[-1] 即表示 sdshdr 结构中的成员 flags,通过 flags 我们可以判断 sdshdr 的类型。
switch 的表达式 flags & SDS_TYPE_MASK, SDS_TYPE_MASK 的定义:#define SDS_TYPE_MASK 7 ,由于这里采用的是按位与操作,所以我们把 7 转换成二进制:000...0111,所以 SDS_TYPE_MASK 的作用就是取 flags 的低3位。
case 表达式的变量就是对应类型的值。

我们看进入 SDS_TYPE_5 时,返回的是一个宏 SDS_TYPE_5_LEN 的结果,SDS_TYPE_5_LEN 的定义:#define SDS_TYPE_5_LEN(f) ((f) >> SDS_TYPE_BITS)SDS_TYPE_BITS 的定义: #define SDS_TYPE_BITS 3,由此我们可以看到,SDS_TYPE_5_LEN 是将 flags 右移三位,即返回 0。因为 sdshdr5 是不使用的,所以对其长度也返回 0.

其他 case 分支就是返回它们的 len 变量。

sdsavail

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 的可用空间。sds 与传统的C字符串不同,C字符串以 ‘\0’ 结尾,而 sds 则是为字符串多分配了一段空间,减少之后增容所带来的开销。

sdssetlen

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 的有效长度。

sdsinclen

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 的有效长度。

sdsalloc

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 的已分配空间大小。也可以通过 sdsavail() + sdslen() 来获取

sdssetalloc

static inline void sdssetalloc(sds s, size_t newlen) {
    unsigned char flags = s[-1];
    switch(flags&SDS_TYPE_MASK) {
        case SDS_TYPE_5:
            /* Nothing to do, this type has no total allocation info. */
            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;
    }
}

设置 sds 的已分配空间大小

创建/修改/销毁 sds(sds.c)

要创建一个 sds 对象,首先要确认其 sds 结构属于哪一种,所以我们要根据字符串长度来选择 sdshdr 。

下面是根据字符串长度来确认 sds 类型的几个相关函数:

sdsReqType

static inline char sdsReqType(size_t string_size) {
    if (string_size < 1<<5)
        return SDS_TYPE_5;
    if (string_size < 1<<8)
        return SDS_TYPE_8;
    if (string_size < 1<<16)
        return SDS_TYPE_16;
#if (LONG_MAX == LLONG_MAX) // 64位
    if (string_size < 1ll<<32)
        return SDS_TYPE_32;
    return SDS_TYPE_64;
#else // 32 位
    return SDS_TYPE_32;
#endif
}

根据字符串长度来确定 sds 的类型。

我们看其中有一个 #if 的预处理符号,在不同机器下(32 位与 64 位)返回不同结果。long 和 size_t 在 32位下是4字节,64位下是8字节

sdsHdrSize

static inline int sdsHdrSize(char type) {
    switch(type&SDS_TYPE_MASK) {
        case SDS_TYPE_5:
            return sizeof(struct sdshdr5);
        case SDS_TYPE_8:
            return sizeof(struct sdshdr8);
        case SDS_TYPE_16:
            return sizeof(struct sdshdr16);
        case SDS_TYPE_32:
            return sizeof(struct sdshdr32);
        case SDS_TYPE_64:
            return sizeof(struct sdshdr64);
    }
    return 0;
}

根据 sds 的类型返回对应 sdshdr 的大小

sdsTypeMaxSize

static inline size_t sdsTypeMaxSize(char type) {
    if (type == SDS_TYPE_5)
        return (1<<5) - 1;
    if (type == SDS_TYPE_8)
        return (1<<8) - 1;
    if (type == SDS_TYPE_16)
        return (1<<16) - 1;
#if (LONG_MAX == LLONG_MAX)
    if (type == SDS_TYPE_32)
        return (1ll<<32) - 1;
#endif
    return -1; /* this is equivalent to the max SDS_TYPE_64 or SDS_TYPE_32 */
}

根据 sds 的类型返回其类型最大值

了解这些接口后,我们接下来介绍创建一个 sdshdr 结构体的函数:

sdsnewlen

sds sdsnewlen(const void *init, size_t initlen) {
    return _sdsnewlen(init, initlen, 0);
}

sdsnewlen 是用来创建一个长度为 initlen 的 sds,并使用 init 指向的字符串来初始化 sds ,如果 init 为 NULL,则将 sds 全部初始化为 ‘\0’。可以看到它是调用了 _sdsnewlen 接口,该接口源码如下:

_sdsnewlen

sds _sdsnewlen(const void *init, size_t initlen, int trymalloc) {
    void *sh;
    sds s;
    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);
    unsigned char *fp; /* flags pointer. */
    size_t usable;

    assert(initlen + hdrlen + 1 > initlen); /* Catch size_t overflow */
    sh = trymalloc?
        s_trymalloc_usable(hdrlen+initlen+1, &usable) :
        s_malloc_usable(hdrlen+initlen+1, &usable);
    if (sh == NULL) return NULL;
    if (init==SDS_NOINIT)
        init = NULL;
    else if (!init)
        memset(sh, 0, hdrlen+initlen+1);
    s = (char*)sh+hdrlen;
    fp = ((unsigned char*)s)-1;
    usable = usable-hdrlen-1;
    if (usable > sdsTypeMaxSize(type))
        usable = sdsTypeMaxSize(type);
    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;
            sh->alloc = usable;
            *fp = type;
            break;
        }
        case SDS_TYPE_16: {
            SDS_HDR_VAR(16,s);
            sh->len = initlen;
            sh->alloc = usable;
            *fp = type;
            break;
        }
        case SDS_TYPE_32: {
            SDS_HDR_VAR(32,s);
            sh->len = initlen;
            sh->alloc = usable;
            *fp = type;
            break;
        }
        case SDS_TYPE_64: {
            SDS_HDR_VAR(64,s);
            sh->len = initlen;
            sh->alloc = usable;
            *fp = type;
            break;
        }
    }
    if (initlen && init)
        memcpy(s, init, initlen);
    s[initlen] = '\0';
    return s;
}

trymalloc 参数表示是调用 s_trymalloc_usable 还是 s_malloc_usable ,关于两个函数我们不进行太多研究。

当计算得到 initlen 对应的 sds 类型后,如果是 SDS_TYPE_5 ,则将类型转变为 SDS_TYPE_8 ,随后计算对应的 sdshdr 大小 hdrlen,申请的空间为 hdrlen + initlen+ 1,hdrlen 是 sdshdr 结构体的大小,initlen 是字符串的长度,1是字符串结尾 ‘\0’ 的大小。

随后是对 sdshdr 成员的赋值。在除了 case SDS_TYPE_5 的其他 case 分支中,都有用到宏 SDS_HDR_VAR ,上面我们提到了它创建一个指向包含 s 的结构体指针,后面的 sh 不是函数一开始创建的 sh,而是宏创建的 sh。

其他创建 sds 的函数:

/* Create an empty (zero length) sds string. Even in this case the string
 * always has an implicit null term. */
sds sdsempty(void) {
    return sdsnewlen("",0);
}

/* Create a new sds string starting from a null terminated C string. */
sds sdsnew(const char *init) {
    size_t initlen = (init == NULL) ? 0 : strlen(init);
    return sdsnewlen(init, initlen);
}

/* Duplicate an sds string. */
sds sdsdup(const sds s) {
    return sdsnewlen(s, sdslen(s));
}

sdsempty 是创建一个空的 sds 对象。

sdsnew 则是根据 init 指向的内容来创建 sds对象。如果 init 为空,则 initlen 为0,如果不为空,则按照 init 指向的字符数组创建 sds 对象。

sdsup 则是根据已存在的 sds 对象来创建一个sds 对象

销毁 sds 的函数

/* 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_HDR 操作,即返回一个指向 s 所在结构体对象的指针

修改容量

增容

/* Enlarge the free space at the end of the sds string more than needed,
 * This is useful to avoid repeated re-allocations when repeatedly appending to the sds. */
sds sdsMakeRoomFor(sds s, size_t addlen) {
    return _sdsMakeRoomFor(s, addlen, 1);
}

/* Unlike sdsMakeRoomFor(), this one just grows to the necessary size. */
sds sdsMakeRoomForNonGreedy(sds s, size_t addlen) {
    return _sdsMakeRoomFor(s, addlen, 0);

sdsMakeRoomForsdsMakeRoomForNonGreedy 为 s 增加 adlen 个字节的长度,可以看到这两个函数都是调用了 _sdsMakeRoomFor 接口,只不过在参数传递上,第三个参数有所不同。增容时如果 sds 中的 buf 空间已经足够容纳添加 addlen 之后的长度,则什么都不做;如果空间不足,则需要增容。第三个参数如果是1,则增容时会多开一些空间,以便未来的增容有空间可用; 如果是0,则只会开辟加上 addlen 之后的空间大小,不会多预留空间。

_sdsMakeRoomFor
sds _sdsMakeRoomFor(sds s, size_t addlen, int greedy) {
    void *sh, *newsh;
    size_t avail = sdsavail(s);
    size_t len, newlen, reqlen;
    char type, oldtype = s[-1] & SDS_TYPE_MASK;
    int hdrlen;
    size_t usable;

    /* Return ASAP if there is enough space left. */
    if (avail >= addlen) return s;

    len = sdslen(s);
    sh = (char*)s-sdsHdrSize(oldtype);
    reqlen = newlen = (len+addlen);
    assert(newlen > len);   /* Catch size_t overflow */
    if (greedy == 1) {
        if (newlen < SDS_MAX_PREALLOC)
            newlen *= 2;
        else
            newlen += SDS_MAX_PREALLOC;
    }

    type = sdsReqType(newlen);

    /* Don't use type 5: the user is appending to the string and type 5 is
     * not able to remember empty space, so sdsMakeRoomFor() must be called
     * at every appending operation. */
    if (type == SDS_TYPE_5) type = SDS_TYPE_8;

    hdrlen = sdsHdrSize(type);
    assert(hdrlen + newlen + 1 > reqlen);  /* Catch size_t overflow */
    
    if (oldtype==type) {
        newsh = s_realloc_usable(sh, hdrlen+newlen+1, &usable);
        if (newsh == NULL) return NULL;
        s = (char*)newsh+hdrlen;
    } else {
        /* Since the header size changes, need to move the string forward,
         * and can't use realloc */
        newsh = s_malloc_usable(hdrlen+newlen+1, &usable);
        if (newsh == NULL) return NULL;
        memcpy((char*)newsh+hdrlen, s, len+1);
        s_free(sh);
        s = (char*)newsh+hdrlen;
        s[-1] = type;
        sdssetlen(s, len);
    }
    usable = usable-hdrlen-1;
    if (usable > sdsTypeMaxSize(type))
        usable = sdsTypeMaxSize(type);
    sdssetalloc(s, usable);
    return s;
}

在这里我们可以看到《Redis 设计与实现》提到的 sds 预留空间策略(greedy == 1)的两种情况:

  1. newlen < SDS_MAX_PREALLOC 时(SDS_MAX_PREALLOC 的定义:#define SDS_MAX_PREALLOC (1024*1024)),即当新长度小于 1MB 时,预留空间长度为 2 * newlen
  2. newlen >= SDS_MAX_PREALLOC ,预留空间长度为 1MB

增容后的 sds 可能长度超越原 sdshdr 的最大长度,此时就要为 sds 分配一个新的 sdshdr。

去除为 sds 预留的空间

sds sdsRemoveFreeSpace(sds s) {
    void *sh, *newsh;
    char type, oldtype = s[-1] & SDS_TYPE_MASK;
    int hdrlen, oldhdrlen = sdsHdrSize(oldtype);
    size_t len = sdslen(s);
    size_t avail = sdsavail(s);
    sh = (char*)s-oldhdrlen;

    /* Return ASAP if there is no space left. */
    if (avail == 0) return s;

    /* Check what would be the minimum SDS header that is just good enough to
     * fit this string. */
    type = sdsReqType(len);
    hdrlen = sdsHdrSize(type);

    /* If the type is the same, or at least a large enough type is still
     * required, we just realloc(), letting the allocator to do the copy
     * only if really needed. Otherwise if the change is huge, we manually
     * reallocate the string to use the different header type. */
    if (oldtype==type || type > SDS_TYPE_8) {
        newsh = s_realloc(sh, oldhdrlen+len+1);
        if (newsh == NULL) return NULL;
        s = (char*)newsh+oldhdrlen;
    } else {
        newsh = s_malloc(hdrlen+len+1);
        if (newsh == NULL) return NULL;
        memcpy((char*)newsh+hdrlen, s, len+1);
        s_free(sh);
        s = (char*)newsh+hdrlen;
        s[-1] = type;
        sdssetlen(s, len);
    }
    sdssetalloc(s, len);
    return s;
}

调用该函数后,不可再使用传入的 s,而应该使用返回的s,其他引用传入的 s 也应该修改指向,因为原来空间会被释放。

只有发生巨大的空间缩短时,sdshdr 才会修改

重新分配 sds 的空间

sds sdsResize(sds s, size_t size) {
    void *sh, *newsh;
    char type, oldtype = s[-1] & SDS_TYPE_MASK;
    int hdrlen, oldhdrlen = sdsHdrSize(oldtype);
    size_t len = sdslen(s);
    sh = (char*)s-oldhdrlen;

    /* Return ASAP if the size is already good. */
    if (sdsalloc(s) == size) return s;

    /* Truncate len if needed. */
    if (size < len) len = size;

    /* Check what would be the minimum SDS header that is just good enough to
     * fit this string. */
    type = sdsReqType(size);
    /* Don't use type 5, it is not good for strings that are resized. */
    if (type == SDS_TYPE_5) type = SDS_TYPE_8;
    hdrlen = sdsHdrSize(type);

    /* If the type is the same, or can hold the size in it with low overhead
     * (larger than SDS_TYPE_8), we just realloc(), letting the allocator
     * to do the copy only if really needed. Otherwise if the change is
     * huge, we manually reallocate the string to use the different header
     * type. */
    if (oldtype==type || (type < oldtype && type > SDS_TYPE_8)) {
        newsh = s_realloc(sh, oldhdrlen+size+1);
        if (newsh == NULL) return NULL;
        s = (char*)newsh+oldhdrlen;
    } else {
        newsh = s_malloc(hdrlen+size+1);
        if (newsh == NULL) return NULL;
        memcpy((char*)newsh+hdrlen, s, len);
        s_free(sh);
        s = (char*)newsh+hdrlen;
        s[-1] = type;
    }
    s[len] = 0;
    sdssetlen(s, len);
    sdssetalloc(s, size);
    return s;
}

如果 size 小于当前已经使用的字符串长度,则字符串会被截断。

sds 的操作 (sds.c)

这里只挑一些笔者认为具有代表的函数进行讲述。

拼接

/* Append the specified null terminated C string to the sds string 's'.
 *
 * After the call, the passed sds string is no longer valid and all the
 * references must be substituted with the new pointer returned by the call. */
sds sdscat(sds s, const char *t) {
    return sdscatlen(s, t, strlen(t));
}

/* Append the specified sds 't' to the existing sds 's'.
 *
 * After the call, the modified sds string is no longer valid and all the
 * references must be substituted with the new pointer returned by the call. */
sds sdscatsds(sds s, const sds t) {
    return sdscatlen(s, t, sdslen(t));
}

sdscat 是将一个C字符串拼接到 sds 之后,调用了 sdscatlen 来完成拼接

sdscatsds 是将 sds 拼接到另一个 sds 之后,同样也调用了 sdscatlen 来完成

这两者调用后,都不能再使用原来的 sds ,因为在 sdscatlen 中进行了扩容,所以原空间销毁,所有引用原 sds 的部分都要替换成返回的 sds

sdscatlen

/* Append the specified binary-safe string pointed by 't' of 'len' bytes to the
 * end of the specified sds string 's'.
 *
 * After the call, the passed sds string is no longer valid and all the
 * references must be substituted with the new pointer returned by the call. */
sds sdscatlen(sds s, const void *t, size_t len) {
    size_t curlen = sdslen(s);

    s = sdsMakeRoomFor(s,len);
    if (s == NULL) return NULL;
    memcpy(s+curlen, t, len);
    sdssetlen(s, curlen+len);
    s[curlen+len] = '\0';
    return s;
}

sdscatlen 中,先为 s 增了 len 个长度,再在 s 后面拼接 t。

拷贝

/* Destructively modify the sds string 's' to hold the specified binary
 * safe string pointed by 't' of length 'len' bytes. */
sds sdscpylen(sds s, const char *t, size_t len) {
    if (sdsalloc(s) < len) {
        s = sdsMakeRoomFor(s,len-sdslen(s));
        if (s == NULL) return NULL;
    }
    memcpy(s, t, len);
    s[len] = '\0';
    sdssetlen(s, len);
    return s;
}

/* Like sdscpylen() but 't' must be a null-terminated string so that the length
 * of the string is obtained with strlen(). */
sds sdscpy(sds s, const char *t) {
    return sdscpylen(s, t, strlen(t));
}

sdscpylen 将 s 完全修改,即从头开始拷贝 t 的内容。

sdscpy 复用了 sdscpylen 函数,要求参数 t 是以 NULL 结尾的,因为它要通过 strlen 来计算长度


我懒
4 声望4 粉丝