redis代码字符串阅读
今天看了些字符串的实现,感觉有点违反之前的认知
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[];
};
这段代码里面长度为0的char数组buf的作用是什么?我尝试把char buff[] 放到flags之前,发现编译器报了个错,说不能定义一个长度为0的数组。查完发现是一个trick,如果把空数组放在对象的最末尾,那么在申请内存时,可以这么操作:
auto a = (sdshdr64 *)malloc(sizeof(sdshdr64 )+ alloc);
for( int i = 0 ; i < alloc ; ++ i)
{
a->buf[i] = i;
}
这么做的话,整个struct的长度就可以控制,动态分配sdshdr64 中buf的大小。
也就是说sdshdr64 的内存布局是这样的:
0-7 len
8-15 alloc
16 flags
17-alloc+17 buf
然后再来看看sdshdr字符串相关的操作,以长度为例:
#define char* sds
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_TYPE_64部分是这样的:
static inline size_t sdslen(const sds s) {
unsigned char flags = s[-1];
switch(flags&SDS_TYPE_MASK) {
case SDS_TYPE_64:
return ((sdshdr64*)(s-sizeof(sdshdr64)))->len;
}
return 0;
}
按照逻辑反推的话,s[-1]是flags,那么s的指向应该是sdshdr64->buff, 加了__attribute__ ((packed)) 这个属性,强制不要进行对齐,这时sizeof(struct sdshdr64)的长度为17,刚好就是对象内存开始的地方。
了解这些后那些直接操作sds的就很直观了,一个经常用的函数:
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);
if (newlen < SDS_MAX_PREALLOC)
newlen *= 2;
else
newlen += SDS_MAX_PREALLOC;
type = sdsReqType(newlen);
if (type == SDS_TYPE_5) type = SDS_TYPE_8;
hdrlen = sdsHdrSize(type);
if (oldtype==type) {
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);
s_free(sh);
s = (char*)newsh+hdrlen;
s[-1] = type;
sdssetlen(s, len);
}
sdssetalloc(s, newlen);
return s;
}
无非是判断要新加的大小与旧数组大小的关系,决定类型要不要换,分配新的内存(扩大两倍)返回出去。
后面又看了下ad_list相关代码,跟上学时链表的课后作业一样,很基础,没啥好说的