Redis 简单字符串相关源码阅读笔记,源码文件 sdsalloc.h
& sds.h
& sds.c
。
1. 小结
Tips
- 在 C 语言中,可利用柔性数组成员 的特性实现变长数组,而且与使用指针相比,柔性数组地址连续,便于内存申请与释放,较少内存碎片
- 宏定义中的
##
为符号连接操作符
- 关于
static inline
inline
的作用仅仅是建议编译器做内联开展处理,而不是强制
static
告诉编译器其他文件看不到这个函数,因此该函数只能在当前文件中被调用
2. 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
25
26
27
28
29
30
31
32
33
|
typedef char *sds;
// --------------------- 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[];
};
|
可以看出来,sds 本质还是 C 风格字符串,但是由于使用 len
表示长度而不用 \0
作为结尾标志,所以 sds 是二进制安全的。
关于 sds 的头部结构,作者定义了五种不同类型的 sdshdr
供程序使用,区别只是存储 len
和 alloc
使用的整型长度不同,目的应该为了节省内存。
sdshdr5
会在 key
的长度小于 32 时使用,详细可了解 dbAdd -> sdsdup -> sdsnewlen 的代码。
__attribute__ ((__packed__))
的作用就是告诉编译器取消结构体在编译过程中的优化对齐,以紧凑模式来分配内存。按照实际占用字节数进行对齐,是 GCC 特有的语法。这个功能是跟操作系统没关系,跟编译器有关。
在上述结构体定义中,char buf[]
使用了柔性数组成员 的特性,其长度为 0,不占用额外的内存空间,buf
实际指向的是结构体之后的内存空间,如果给这个结构体分配的内容大于这个结构体实际大小,后面多余的部分就是这个 buf
的内容,也可用此类特性实现 C 语言变长数组。
3. sds 构造函数
3.1. sdsnewlen
根据指针 init
和长度 initlen
构造一个 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
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
|
sds sdsnewlen(const void *init, size_t initlen) {
void *sh; /* 头部指针 */
sds s;
/* 根据 initlen 大小选择合适的 sdshdr 类型 */
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;
/* 根据 type 返回 sdshdr 的存储空间大小 */
int hdrlen = sdsHdrSize(type);
unsigned char *fp; /* flags pointer. */
/* + 1 是为了存储 \0 */
sh = s_malloc(hdrlen+initlen+1);
if (sh == NULL) return NULL;
if (init==SDS_NOINIT)
init = NULL;
else if (!init)
memset(sh, 0, hdrlen+initlen+1);
/* s 指向 buf 数组首地址 */
s = (char*)sh+hdrlen;
/* flags pointer */
fp = ((unsigned char*)s)-1;
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 = initlen;
*fp = type;
break;
}
case SDS_TYPE_16: {
SDS_HDR_VAR(16,s);
sh->len = initlen;
sh->alloc = initlen;
*fp = type;
break;
}
case SDS_TYPE_32: {
SDS_HDR_VAR(32,s);
sh->len = initlen;
sh->alloc = initlen;
*fp = type;
break;
}
case SDS_TYPE_64: {
SDS_HDR_VAR(64,s);
sh->len = initlen;
sh->alloc = initlen;
*fp = type;
break;
}
}
if (initlen && init)
memcpy(s, init, initlen); /* 填充 buf 字符数组的内容 */
s[initlen] = '\0'; /* 填充终止字符 \0 */
return s;
}
|
-
首先根据 initlen
大小,选择合适的 sdshdr
类型
-
分配的实际内存大小为 hdrlen + initlen + 1
-
如果 init = NULL
,则字符串每个字节初始化为 0
-
sds 指向的是 char buf[]
的首地址
-
生成的 sds 中有:len = alloc = initlen
-
为了兼容 C 风格字符串,sds 也以 \0
结尾,所以 sds 可以直接重用一部分 C 字符串函数库里面的函数
-
sds 结构如下所示
1
2
3
4
5
6
7
8
9
|
+-- sizeof(struct sdshdr) --+
| |
| |
+--------+---------+--------+------------+
| len | alloc | flags | char buf[] |
+--------+---------+--------+------------+
^ ^ ^
| | |
s-hdrlen s-1 s
|
3.2. sdsempty
构造一个空的 sds,可以看出 buf
数组长度即使为 0,该 sds 也包含 \0
。
1
2
3
|
sds sdsempty(void) {
return sdsnewlen("",0);
}
|
3.3. sdsnew
根据一个 C 风格字符串构造一个 sds
1
2
3
4
|
sds sdsnew(const char *init) {
size_t initlen = (init == NULL) ? 0 : strlen(init);
return sdsnewlen(init, initlen);
}
|
3.4. sdsdup
复制一个 sds
1
2
3
|
sds sdsdup(const sds s) {
return sdsnewlen(s, sdslen(s));
}
|
3.5. sdsfromlonglong
将一个 long long
类型转换为 sds,该操作比直接调用 sdscatprintf(sdsempty(),"%lld\n", value);
高效得多。
1
2
3
4
5
6
7
8
9
10
11
|
sds sdsfromlonglong(long long value) {
/* #define SDS_LLSTR_SIZE 21 */
char buf[SDS_LLSTR_SIZE];
/* int sdsll2str(char *s, long long value)
* The function returns the length of the null-terminated string
* representation stored at 's'.
* 's' must point to a string with room for at least SDS_LLSTR_SIZE bytes. */
int len = sdsll2str(buf,value);
return sdsnewlen(buf,len);
}
|
4. Set/Get APIs
4.1. sdslen
获取 buf
数组已使用长度 len
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
/* 由指向 buf 的指针,获取 sdshdr 的起始地址 */
#define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))))
/* 得到 sdshdr5 的长度 */
#define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS)
static inline size_t sdslen(const sds s) {
/* 获取 flags 的值
* 由于结构体定义使用紧凑模式,所以 s 所指的地址紧接在存储 flags 的地址之后
* 从而 s[-1] 的内容即为 flags,注意不要和 Python 中的 -1 混淆 */
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;
}
|
Tips: 宏定义中的 ##
为符号连接操作符。
Tips: 关于 static inline
inline
的作用仅仅是建议编译器做内联开展处理,而不是强制。内联函数 (inline) 可以减少 CPU 的系统开销,并且程序的整体速度将加快,但当内联函数很大时,会有相反的作用,因此一般比较小的函数才使用内联函数。通常,程序执行时,处理器从内存中读取代码执行。当程序中调用一个函数时,程序跳到存储器中保存函数的位置,开始读取代码执行,执行完后再返回。为了提高速度,C 语言定义了 inline
函数,告诉编译器把函数代码在编译时直接拷贝到程序中,这样就不用执行时另外读取函数代码。
static
告诉编译器其他文件看不到这个函数,因此该函数只能在当前文件中被调用。
4.2. sdsavail
获取 buf
数组中的剩余可用空间大小
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
|
/* 由指向 buf 的指针,获取 sdshdr 的起始地址 */
#define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (void*)((s)-(sizeof(struct sdshdr##T)));
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_HDR_VAR
而不使用 return SDS_HDR(T,s)->alloc - SDS_HDR(T,s)->len;
的原因可能如下:
而 SDS_HDR_VAR
的宏定义最后的分号其实可以去掉,详情请见
4.3. sdssetlen – 设置 len
1
|
static inline void sdssetlen(sds s, size_t newlen);
|
4.4. sdsinclen – 增加 len
1
2
|
// newlen = len + inc
static inline void sdsinclen(sds s, size_t inc);
|
4.5. sdsupdatelen
更新 sds 的长度 len
,由于是调用 strlen
,故只统计到终止字符 \0
(不包括 \0
)
1
2
3
4
|
void sdsupdatelen(sds s) {
size_t reallen = strlen(s);
sdssetlen(s, reallen);
}
|
4.6. sdsalloc – 获取 alloc
1
2
|
/* sdsalloc() = sdsavail() + sdslen() */
static inline size_t sdsalloc(const sds s);
|
4.7. sdssetalloc – 设置 alloc
1
|
static inline void sdssetalloc(sds s, size_t newlen);
|
4.8. sdsAllocSize
返回分配内存空间的大小,包括:
- 头部结构体的大小
sdsHdrSize
buf
数组容量 alloc
- 终止字符
\0
的长度 1
1
2
3
4
|
size_t sdsAllocSize(sds s) {
size_t alloc = sdsalloc(s);
return sdsHdrSize(s[-1])+alloc+1;
}
|
4.9. sdsAllocPtr
返回 sds 头部起始地址。
1
2
3
|
void *sdsAllocPtr(sds s) {
return (void*) (s-sdsHdrSize(s[-1]));
}
|
5. sds 析构函数
5.1. sdsclear – 虚假的析构函数
将 buf
数组长度设置为 0,但是可以发现并未调用 free
,而是将其设置为空闲空间。
1
2
3
4
|
void sdsclear(sds s) {
sdssetlen(s, 0);
s[0] = '\0';
}
|
5.2. sdsfree – 真实的析构函数
1
2
3
4
5
|
void sdsfree(sds s) {
if (s == NULL) return;
/* sdsHdrSize --- 头部大小 */
s_free((char*)s-sdsHdrSize(s[-1]));
}
|
6. 动态调整函数
6.1. sdsMakeRoomFor – 扩容
不改变 len
,扩大可用空间 avail
大小,使其大于 addlen
。
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
sds sdsMakeRoomFor(sds s, size_t addlen) {
void *sh, *newsh;
size_t avail = sdsavail(s);
size_t len, newlen;
char type, oldtype = s[-1] & SDS_TYPE_MASK;
int hdrlen;
/* Return ASAP if there is enough space left. */
if (avail >= addlen) return s;
len = sdslen(s);
sh = (char*)s-sdsHdrSize(oldtype);
newlen = (len+addlen);
/* #define SDS_MAX_PREALLOC (1024*1024)
* 扩容策略:如果 buf 数组未来长度小于 1 M,按所需的两倍扩容
* 如果 buf 数组未来长度不小于 1 M,比要求多扩容 1 M */
if (newlen < SDS_MAX_PREALLOC)
newlen *= 2;
else
newlen += SDS_MAX_PREALLOC;
type = sdsReqType(newlen); /* new type */
/* 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); /* new hdrlen */
if (oldtype==type) {
/* 不需要更改头部的 len 和 flags,直接扩充 buf 数组的容量 */
newsh = s_realloc(sh, hdrlen+newlen+1);
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(hdrlen+newlen+1);
if (newsh == NULL) return NULL;
memcpy((char*)newsh+hdrlen, s, len+1); /* len + 1,\0 也被复制 */
s_free(sh);
s = (char*)newsh+hdrlen;
s[-1] = type;
sdssetlen(s, len);
}
sdssetalloc(s, newlen);
return s;
}
|
可以发现该函数仅在当前剩余可用空间不足时进行扩容,采取的扩容策略类似 vector
,可以在尽可能不浪费内存空间的基础上减少扩容操作的次数。
6.2. sdsRemoveFreeSpace – 回收空闲空间
1
|
sds sdsRemoveFreeSpace(sds s);
|
- 新的头部结构为
sdshdr8
且旧的头部结构不为 sdshdr8
时,调用 s_malloc
重新申请新的内存空间,不然只是在旧的 sds 基础上调用 s_realloc
调整 buf
数组的容量,使得 alloc = len
成立
- 注意调用
sdsRemoveFreeSpace
成功返回后,原指针会失效,请使用返回的新指针
6.3. sdsIncrLen – 调整长度
1
|
void sdsIncrLen(sds s, ssize_t incr)
|
需要注意的是,变量 incr
可正可负
incr >= 0
: 增长时需确保剩余可用空间大小充足
incr < 0
: 缩短时需确保减少量不得超过现有长度
7. Append 操作
7.1. sdsgrowzero
将 buf
的数组长度增长到给定的 len
,如果 len < currentLen
,则什么都不做。新增的字节部分,每一位都置为 0。
1
|
sds sdsgrowzero(sds s, size_t len);
|
7.2. sdscatlen
将二进制安全的字符串附加到现有的 buf
数组之后,注意该函数调用成功后,原指针会失效,请使用返回的新指针。
1
|
sds sdscatlen(sds s, const void *t, size_t len);
|
7.3. sdscat
将 C 风格字符串附加到现有的 buf
数组之后,注意该函数调用成功后,原指针会失效,请使用返回的新指针。
1
2
3
|
sds sdscat(sds s, const char *t) {
return sdscatlen(s, t, strlen(t));
}
|
7.4. sdscatsds
拼接两个 sds,注意该函数调用成功后,原指针会失效,请使用返回的新指针。
1
2
3
|
sds sdscatsds(sds s, const sds t) {
return sdscatlen(s, t, sdslen(t));
}
|
7.5. sdscatvprintf
将格式化字符串拼接到 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
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
sds sdscatvprintf(sds s, const char *fmt, va_list ap) {
va_list cpy;
char staticbuf[1024], *buf = staticbuf, *t;
size_t buflen = strlen(fmt)*2;
/* We try to start using a static buffer for speed.
* If not possible we revert to heap allocation. */
if (buflen > sizeof(staticbuf)) {
buf = s_malloc(buflen);
if (buf == NULL) return NULL;
} else {
buflen = sizeof(staticbuf);
}
/* Try with buffers two times bigger every time we fail to
* fit the string in the current buffer size. */
while(1) {
/* 设置哨兵,测试格式化字符串是否已全部写入 buf 中 */
buf[buflen-2] = '\0';
/* void va_copy(va_list dest, va_list src);
* The va_copy macro copies src to dest. */
va_copy(cpy,ap);
/* int vsnprintf(char *restrict buffer,
* size_t bufsz,
* const char *restrict format,
* va_list vlist );
* writes the results to a character string buffer. At most buflen - 1
* characters are written. The resulting character string will be
* terminated with a null character, unless buflen is zero. */
vsnprintf(buf, buflen, fmt, cpy);
/* void va_end(va_list ap);
* The va_end macro performs cleanup for an ap object initialized
* by a call to va_start or va_copy */
va_end(cpy);
if (buf[buflen-2] != '\0') { /* 当前 buf 不够长! */
if (buf != staticbuf) s_free(buf);
buflen *= 2;
buf = s_malloc(buflen);
if (buf == NULL) return NULL;
continue;
}
break; /* 格式化字符串已全部写入 buf */
}
/* Finally concat the obtained string to the SDS string and return it. */
t = sdscat(s, buf);
if (buf != staticbuf) s_free(buf);
return t;
}
|
由于不知晓格式化字符串的长度,所以我们需要使用一个 while
循环来测试当前分配的 buf
字符数组的容量是否可以容纳该格式化字符串,每次失败时,我们将 buf
的容量翻倍。
7.6. sdscatprintf
将格式化字符串拼接到 sds 之后,注意该函数调用成功后,原指针会失效,请使用返回的新指针。
1
|
sds sdscatprintf(sds s, const char *fmt, ...);
|
7.7. sdscatfmt
将格式化字符串拼接到 sds 之后,比 sdscatprintf
更快,但是其仅支持以下几种格式化字符串:
%s
- C String
%S
- SDS string
%i
- signed int
%I
- 64 bit signed integer (long long, int64_t)
%u
- unsigned int
%U
- 64 bit unsigned integer (unsigned long long, uint64_t)
%%
- Verbatim “%” character.
1
|
sds sdscatfmt(sds s, char const *fmt, ...);
|
7.8. sdscatrepr
将无法打印显式字符的字符数组的转义形式 (eg, “\n\r\a…” or “\x<hex-number>") 拼接到 sds 之后,注意该函数调用成功后,原指针会失效,请使用返回的新指针。
1
|
sds sdscatrepr(sds s, const char *p, size_t len);
|
7.9. sdsjoin
使用 C 风格字符串 sep
作为分隔符,将 C 风格字符串数组拼接为一个 sds。
1
|
sds sdsjoin(char **argv, int argc, char *sep);
|
7.10. sdsjoinsds
类似于 sdsjoin
,将 sds 字符串数组拼接为一个 sds。值得注意的是,使用的字符串分隔符是二进制安全的,而不是 C 风格字符串。
1
|
sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen);
|
8. 拷贝操作
8.1. sdscpylen
丢弃 sds 字符数组中的原内容,将长为 len
的字符串拷贝至 sds 的 buf
中。
1
|
sds sdscpylen(sds s, const char *t, size_t len);
|
8.2. sdscpy
丢弃 sds 字符数组中的原内容,将 C 风格字符串拷贝至 sds 的 buf
中。
1
2
3
|
sds sdscpy(sds s, const char *t) {
return sdscpylen(s, t, strlen(t));
}
|
9. 修剪操作
9.1. sdstrim
去除 sds 两端出现在 C 风格字符串 cset
中的字符。注意该函数调用成功后,原指针会失效,请使用返回的新指针。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
sds sdstrim(sds s, const char *cset) {
char *start, *end, *sp, *ep;
size_t len;
sp = start = s; /* 指向第一个字符 */
ep = end = s+sdslen(s)-1; /* 指向终止字符 `\0` 前一字符 */
/* char *strchr(const char *str, int ch);
* Finds the first occurrence of `ch` (after conversion to char as if
* by `(char)ch`) in the null-terminated byte string pointed to by `str`
* (each character interpreted as unsigned char).
* 从前往后遍历 sds,如果当前指针所指字符出现在 `cset` 所指字符串中,则指针 +1 */
while(sp <= end && strchr(cset, *sp)) sp++;
/* 前一步遍历的终止条件不是越界时, 有 ep > sp
* 从后往前遍历 sds,如果当前指针所指字符出现在 `cset` 所指字符串中,则指针 -1 */
while(ep > sp && strchr(cset, *ep)) ep--;
len = (sp > ep) ? 0 : ((ep-sp)+1);
/* void* memmove( void* dest, const void* src, size_t count );
* Copies count characters from the object pointed to by `src` to the object
* pointed to by `dest` */
if (s != sp) memmove(s, sp, len);
s[len] = '\0';
sdssetlen(s,len);
return s;
}
|
可以看出在移除指定的字符后,并未改变 sds 所申请的内存空间大小,可能会造成内存碎片。
9.2. sdsrange
依据 start
和 end
索引下标修剪 sds 字符串,注意:
start
和 end
可以为负数,类似数组的下标索引
- 结果子串的范围为闭区间
[start, end]
1
|
void sdsrange(sds s, ssize_t start, ssize_t end);
|
10. split 操作
10.1. sdssplitlen
使用长为 seplen
的二进制安全字符串 sep
作为分隔符,将长为 len
的二进制安全字符串 s
分割成 count
个 sds 字符串。
1
|
sds *sdssplitlen(const char *s, ssize_t len, const char *sep, int seplen, int *count)
|
10.2. sdsfreesplitres
1
2
3
4
5
6
7
|
/* Free the result returned by sdssplitlen(), or do nothing if 'tokens' is NULL. */
void sdsfreesplitres(sds *tokens, int count) {
if (!tokens) return;
while(count--)
sdsfree(tokens[count]);
s_free(tokens);
}
|
10.3. sdsplitargs
- Split a line into arguments, where every argument can be in the following programming-language REPL-alike form:
foo bar "newline are supported\n" and "\xff\x00otherstuff"
- The number of arguments is stored into
*argc
, and an array of sds is returned.
- The caller should free the resulting array of sds strings with sdsfreesplitres().
1
|
sds *sdssplitargs(const char *line, int *argc)
|
11. Utils
11.1. sdstolower – 转换为小写
1
2
3
4
|
void sdstolower(sds s) {
size_t len = sdslen(s), j;
for (j = 0; j < len; j++) s[j] = tolower(s[j]);
}
|
11.2. sdstoupper – 转换为大写
1
2
3
4
|
void sdstoupper(sds s) {
size_t len = sdslen(s), j;
for (j = 0; j < len; j++) s[j] = toupper(s[j]);
}
|
11.3. sdscmp
使用 memcmp
实现两个 sds 的比较操作
1
2
3
4
5
6
7
8
9
10
11
|
int sdscmp(const sds s1, const sds s2) {
size_t l1, l2, minlen;
int cmp;
l1 = sdslen(s1);
l2 = sdslen(s2);
minlen = (l1 < l2) ? l1 : l2;
cmp = memcmp(s1,s2,minlen);
if (cmp == 0) return l1>l2? 1: (l1<l2? -1: 0);
return cmp;
}
|
11.4. sdsmapchars
遍历 sds 字符串,将在字符串 from
中出现的字符替换成 to
中对应位置的字符,setlen
为字符串 from
和 to
的长度。
1
|
sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen);
|