Redis【2】- SDS源码剖析
1 简介&根底用法
Redis 中用得最多的便是字符串,在 C 言语中其实能够直接运用 char*
字符数组来完成字符串,也有许多能够直接运用得函数。可是 Redis 并没有运用 C 言语原生的字符串,而是自己完成了一个 SDS(简略动态字符串,Simple Dynamic String) 。
Redis 的 SDS 兼容了 C 言语的字符串类型的用法,
下面是 Redis 中 string 类型最常用的用法:
本地:0>set hello world
OK
本地:0>get hello
world
本地:0>type hello
string
本地:0>strlen hello
5
2 为什么 Redis 自完成字符串?
2.1 存储二进制的约束
C 言语的 char*
是以 \0
作为完毕字符串的标识,假如需求存储的数据中自身就含有 \0
,那就没有办法正确表明,而像图画这种数据,一般存储下来都是二进制格局的,所以 Redis 不能直接运用 char*
。
下面是 C 言语的 \0
对字符串长度判别的影响:
#include "stdio.h"
#include "string.h"
int main(void) {
char *a = "hello\0Wolrd";
char *b = "helloWolrd\0";
printf("字符串的长度:%lu\n",
strlen(a)
); printf("字符串的长度:%lu\n",
strlen(b)
);}
输出成果则会不一样,\0
后边的数据会被切断:
字符串的长度:5
字符串的长度:10
在 SDS 结构中却能确保二进制安全,由于 SDS 保存了 len 特点,这就能够不适用 \0
这个标识来判别字符串是否完毕。
2.2 操作功率问题
2.2.1 空间功率
2.2.1.1 预分配内存
原生的 C 言语字符串,在添加的时分,或许会由于可用空间缺乏,无法添加,而 Redis 追加字符串的时分,运用了预分配的战略,假如内存不行,先进行内存拓宽,再追加,有用削减修正字符串带来的内存从头分配的次数。
相似于 Java 中的 ArrayList,采纳预分配,内部实在的容量一般都是大于实践的字符串的长度的,当字符串的长度小于 1MB 的时分,假如内存不行,扩容都是加倍现在的空间;假如字符串的长度现已超过了 1MB,扩容的时分也只会多扩 1MB 的空间,可是最大的字符串的长度是 512MB。
2.2.1.2 慵懒空间开释
慵懒空间开释用于优化 SDS 的字符串缩短操作,当 SDS 的 API 需求缩短字符串保存的字符串的时分,程序并不会当即运用内存从头分配来回缩短多出来的字节,而是运用 free 特点将这些字节的数量记载下来,并等候将来运用。
当然 SDS 也供给了 SDS 显式调用,真实的开释未运用的空间。
2.2.2 操作功率
原生的 C 言语在获取字符的长度的时分,底层实践是遍历,时刻复杂度是 O(n)
,String 作为 Redis 用得最多的数据类型,获取字符串的长度是比较频频的操作,必定不能这么干,那就用一个变量把 String 的长度存储起来,获取的时分时刻复杂度是 O(1)
。
2.2.3 兼容性较好
Redis 尽管运用了 \0
来结束,可是 sds 字符串的结束仍是会遵从 c 言语的常规,所以能够重用一部分<string. h> 的函数。比方比照的函数 strcasecmp
,能够用来比照 SDS 保存的字符串是否和别的一个字符串是否相同。
strcasecmp(sds->buf,"hello world");
3 源码解读
3.1 简略指针介绍
数组的指针操作:
#include <stdio.h>
int main() {
printf("Hello, World!\n");
char t[] = {'a','b','c','d'};
char* s = t+1; // 指针行进一位
char bb = s[0];
char cc = s[1];
char dd = s[2];
char flag = s[-1]; // 指针撤退一位等价于 char flag = *(s - 1); 或许 char *flag = s - 1; printf("%c %c %c %c", *flag, bb, cc ,dd);
printf("%c %c %c %c", flag, bb, cc ,dd);
return 0;
}
终究输出成果:
Hello, World!
a b c d
3.1.1 sdshdr 奇妙的结构规划
SDS 的相关代码首要在下面两个文件:
- sds. h:头文件
- sds. c:源文件
SDS 界说在 sds. h
中,为了兼容 C 风格的字符串,给 char 取了单个名叫 sds
:
typedef char *sds;
《Redis 规划与完成》中,解说的是 Redis 3.0 的代码,说到 sds 的完成结构 sdshdr 是这样的:
struct sdshdr {
// 记载buf数组已运用字节的数量
// 等于SDS所保存字符串的长度
int len;
// 记载buf数组中未运用的字节数
int free;
// 字节数组,用于保存字符串
char buf[];
};
可是实践上 7.0 版别现已是长这样:
-
- Sdshdr5 从未被运用,咱们仅仅直接拜访标志字节。
- 但是,这儿文档标识 sdshdr5 的结构。
-
- 结构界说运用了__attribute__ ((packed))声明为非内存对齐, 紧凑摆放方法(撤销编译阶段的内存优化对齐功用)
-
- 假如界说了一个
sds *s
, 能够非常便利的运用s[-1]
获取到 flags 地址,防止了在上层调用各种类型判别。
- 假如界说了一个
struct __attribute__ ((__packed__)) sdshdr5 {
unsigned char flags; /* 低 3 位存储类型,高 5 位存储字符串长度 */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {
uint8_t len; /* 已运用 */
uint8_t alloc; /* 总分配的,不包括头部和空的终止符*/
unsigned char flags; /* 低 3 位存储类型,高 5 位预留,还没运用 */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr16 {
uint16_t len; /* 已运用 */
uint16_t alloc; /* 总分配的,不包括头部和空的终止符*/
unsigned char flags; /* 低 3 位存储类型,高 5 位预留,还没运用 */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
uint32_t len; /* 已运用 */
uint32_t alloc; /* 总分配的,不包括头部和空的终止符*/
unsigned char flags; /* 低 3 位存储类型,高 5 位预留,还没运用 */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
uint64_t len; /* used */
uint64_t alloc; /* 总分配的,不包括头部和空的终止符*/
unsigned char flags; /* 低 3 位存储类型,高 5 位预留,还没运用 */
char buf[];
};
// 类型界说总共占用了 0,1,2,3,4 五个数字,也便是三位就能够标识,
// 那么咱们能够运用 flags&SDS_TYPE_MASK 来获取动态字符串对应的字符串类型
#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_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))))
#define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS)
上面界说了 4 种结构体,**Redis 依据不同的字符串的长度,来挑选适宜的结构体,每个结构体有对应数据部分和头部。
类型总共有这些:
#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
用二进制表明,只需求 3 位即可,这也是为什么上面的结构体 sdshdr5
里边的 flags
字段注释里写的:前三位表明类型,后 5 位用于表明字符串长度。
unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
而其他的 sds
结构体类型,由于长度太长了,存不下,所今后 5 位暂时没有用果,而是别的运用特点存储字符串的长度。
uint8_t len; /* used */
uint8_t alloc; /* excluding the header and null terminator */
3.2 3.2 attribute 的效果是什么?
__attribute__ ((packed))
的效果便是告知编译器撤销结构在编译过程中的优化对齐, 依照实践占用字节数进行对齐,是 GCC 特有的语法。这个功用是跟操作体系不要紧,跟编译器有关,gcc 编译器不是紧凑形式的。
attribute__关键字首要是用来在函数或数据声明中设置其特点。给函数赋给特点的首要意图在于让编译器进行优化。函数声明中的__attribute((noreturn)),便是告知编译器这个函数不会回来给调用者,以便编译器在优化时去掉不必要的函数回来代码。
__attribute__书写特征是:__attribute__前后都有两个下划线,并且后边会紧跟一对括弧,括弧里边是相应的__attribute__参数,其语法格局为:
__attribute__ ((attribute-list))
下面是试验的一些代码,试验环境为 Mac:
#include "stdio.h"
struct One{ char ch; int a;} one;
struct __attribute__ ((__packed__)) Tow{ char ch; int a;} tow;
int main(void) {
printf("int 的内存巨细:%lu\n",
sizeof(int)
); printf("新结构体one的巨细(不紧缩):%lu\n",
sizeof(one)
); printf("新结构体tow的巨细(紧缩):%lu\n",
sizeof(tow)
);}
运转成果:
int 的内存巨细:4
新结构体one的巨细(不紧缩):8
新结构体tow的巨细(紧缩):5
编译器紧缩优化(内存不对齐)后,的确体积从 8 变成了 5,缩小了不少,别看这小小的改动,其实在巨大的数量面前,便是很大的空间优化。
3.3 宏操作
redis 依据前面 sds 规划,界说了一些非常奇妙的宏操作:
3.3.1 经过 sds 获取不同类型 sdshdr 变量
/*
* 宏操作
* SDS_HDR_VAR(8,s);
* 下面是对应宏界说翻译的产品
* struct sdshdr8 *sh = (void*)((s)-(sizeof(struct sdshdr##T)));
* 能够依据指向 buf 的sds变量s得到 sdshdr8 的指针,sh 是创立出来的变量
*/
#define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (void*)((s)-(sizeof(struct sdshdr##T)));
/**
* 和上面相似
* 依据指向buf的sds变量s得到sdshdr的指针,只不过这儿是获取的是指针地址
*/
#define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))))
3.3.2 获取 sdshdr5 字符串类型的长度
/**
* 该函数便是获取sdshdr5字符串类型的长度,由于底子不运用sdshdr5类型,所以需求直接回来空,
* 而flags成员运用最低三位有用位来表明类型,所以让f代表的flags的值右移三位即可
*/
#define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS)
3.3.3 经过 sds 获取 len 的值
/**
* 运用到了撤销编译阶段的内存优化对齐功用,直接运用s[-1]获取到flags成员的值,
* 然后依据flags&&SDS_TYPE_MASK来获取到动态字符串对应的类型从而获取动态字符串的长度。
* SDS_TYPE_5_LEN 比较特别一点,由于结构有点不一样
*/
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;
}
3.4 创立新字符串
创立新的字符串一般是传递初始化的长度:
sds sdsnewlen(const void *init, size_t initlen) {
// 内部封装的函数,最终一个参数是是否测验分配
return _sdsnewlen(init, initlen, 0);
}
下面咱们看详细的函数完成:
创立的回来的是指针,指向的是结构体中 buf 开端的方位,
sds _sdsnewlen(const void *init, size_t initlen, int trymalloc) {
// sh 指向sds分配开端的当地
void *sh;
// s 也是指针,指向 buf 开端的方位
sds s;
// 不同长度回来不同的类型sds
char type = sdsReqType(initlen);
/* Empty strings are usually created in order to append. Use type 8
* since type 5 is not good at this. */
// 空字符串经常被创立出来之后,就会履行append操作,所以用type 8替换掉它,type 5 太短了。
if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;
// 获取整个struct的长度
int hdrlen = sdsHdrSize(type);
// flag 指针,标识sds 是哪一个类型的
unsigned char *fp; /* flags pointer. */
// 可用巨细
size_t usable;
// 防止溢出
assert(initlen + hdrlen + 1 > initlen); /* Catch size_t overflow */
// 分配内存,其间s_trymalloc_usable是调整内存,s_malloc_usable是新分配内存,是两种内存分配的方法,经过参数trymalloc操控(+1 是为了处理 \0)
sh = trymalloc?
s_trymalloc_usable(hdrlen+initlen+1, &usable) :
s_malloc_usable(hdrlen+initlen+1, &usable);
// 分配不成功,提前完毕
if (sh == NULL) return NULL;
// 假如需求完全为空的字符串,直接回来null
if (init==SDS_NOINIT)
init = NULL;
else if (!init)
memset(sh, 0, hdrlen+initlen+1); // 初始化
// s 指向数组 buf 的方位(从结构体往后加上hdrlen便是buf数组最初的方位)
s = (char*)sh+hdrlen;
// buf数组的方位-1,便是flags字段的方位
fp = ((unsigned char*)s)-1;
// 可用空间减去hdrlen(已用空间),再减1(‘\0‘)
usable = usable-hdrlen-1;
// 假如可用空间大于当时结构体中alloc字段的巨细,就运用alloc的最大值
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;
}
3.5 获取可用空间
SDS 和我往常所用到的 C 言语的原生字符串有不同,由于从获取可用空间的计算方法来看,并未考虑到字符串需求以 \0
结束,结构体自身带有长度的成员 len,不需求 \0
来做字符串结束的断定,并且不运用 \0
作为结束有许多优点, 分配的减去运用的即可。
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;
}
3.6 设置 & 添加 sds 的长度
// 设置 sds 的长度
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 的长度
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;
}
}
3.7 设置 & 获取已分配空间巨细
/* sdsalloc() = sdsavail() + sdslen() */
// 获取 sds 现已分配的空间的巨细
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 现已分配的空间的巨细
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;
}
}
3.8 扩展 sds 空间
/**
* 扩展sds字符串结尾的闲暇空间,以便调用者坚信在调用此函数后能够掩盖到字符串结尾 addlen字节,再加上null term的一个字节。
* 假如现已有满足的闲暇空间,这个函数回来时不做任何操作,假如没有满足的闲暇空间,它将分配缺失的部分,乃至更多:
* 当greedy为1时,扩展比需求的更多,以防止将来在增量增加时需求从头分配。
* 当greedy为0时,将其扩展到满足大以便为addlen腾出空间。
* 留意:这不会改动sdslen()回来的sds字符串的长度,而只会改动咱们具有的闲暇缓冲区空间。
*/
// 扩展sds空间
sds _sdsMakeRoomFor(sds s, size_t addlen, int greedy) {
void *sh, *newsh;
// 获取剩下可用的空间
size_t avail = sdsavail(s);
size_t len, newlen, reqlen;
// 获取sds 详细数据类型
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 回溯到sds开端方位
sh = (char*)s-sdsHdrSize(oldtype);
// newlen 为最小需求的长度
reqlen = newlen = (len+addlen);
assert(newlen > len); /* Catch size_t overflow */
// 在newlen小于SDS_MAX_PREALLOC(1M),对newlen进行翻倍,在newlen大于SDS_MAX_PREALLOC的情况下,让newlen加上SDS_MAX_PREALLOC。
if (greedy == 1) {
if (newlen < SDS_MAX_PREALLOC) // 小于1Kb 预分配2倍长度 = newlen + newlen
newlen *= 2;
else
newlen += SDS_MAX_PREALLOC; // 剩余1Mb 预分配 = newlen + 1Mb
}
// 获取新长度的类型
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) {
/**
* 本质上是 运用 zrealloc_usable函数,指针ptr有必要为指向堆内存空间的指针,即由malloc函数、calloc函数或realloc函数分配空间的指针。
* realloc函数将指针p指向的内存块的巨细改动为n字节。
* 1.假如n小于或等于p之前指向的空间巨细,那么。坚持原有状况不变。
* 2.假如n大于本来p之前指向的空间巨细,那么,体系将从头为p从堆上分配一块巨细为n的内存空间,一起,将本来指向空间的内容顺次仿制到新的内存空间上,p之前指向的空间被开释。
* relloc函数分配的空间也是未初始化的。
*/
newsh = s_realloc_usable(sh, hdrlen+newlen+1, &usable);
// 恳求空间失利
if (newsh == NULL) return NULL;
// s指向新sds结构的buf开端方位
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;
// 体系copy,跳过头部结构长度,仿制s的有用数据调集
memcpy((char*)newsh+hdrlen, s, len+1);
// 开释旧空间
s_free(sh);
// s履行新的空间,buf开端方位
s = (char*)newsh+hdrlen;
// flag 赋值 头部的第三个有用字段
s[-1] = type;
// 更新有用数据长度
sdssetlen(s, len);
}
// 实践可用数据空间
usable = usable-hdrlen-1;
if (usable > sdsTypeMaxSize(type))
usable = sdsTypeMaxSize(type);
// 更新分配的空间值
sdssetalloc(s, usable);
return s;
}
3.9 开释剩余空间
/* 对sds中剩余的空间进行开释
* 从头分配sds字符串,使其结尾没有闲暇空间。所包括的字符串坚持不变,
* 但下一个衔接操作将需求从头分配。
* 调用之后,传递的sds字符串不再有用,一切引证有必要用调用回来的新指针替换。
*/
sds sdsRemoveFreeSpace(sds s, int would_regrow) {
return sdsResize(s, sdslen(s), would_regrow);
}
/**
* 调整分配的巨细,这能够使分配更大或更小,假如巨细小于当时运用的len,数据将被切断。
* 当将d_regrow参数设置为1时,它会阻挠运用SDS_TYPE_5,这是在sds或许再次更改时所需求的。
* 不管实践分配巨细怎么,sdsAlloc巨细都将被设置为恳求的巨细,这样做是为了防止在调用者检测到它有剩余的空间时重复调用该函数
*/
sds sdsResize(sds s, size_t size, int would_regrow) {
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);
if (would_regrow) {
/* Don't use type 5, it is not good for strings that are expected to grow back. */
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. */
int use_realloc = (oldtype==type || (type < oldtype && type > SDS_TYPE_8));
size_t newlen = use_realloc ? oldhdrlen+size+1 : hdrlen+size+1;
int alloc_already_optimal = 0