Hacking Strings(copied from redis.io, added two functions to this article)
The implementation of Redis strings is contained in
sds.c
(sds
stands for Simple Dynamic Strings).
The C structure
sdshdr
declared in sds.h
represents a Redis string:struct sdshdr {
long len;
long free;
char buf[];
};
The
buf
character array stores the actual string.
The
len
field stores the length of buf
. This makes obtaining the length of a Redis string an O(1) operation.
The
free
field stores the number of additional bytes available for use.
Together the
len
and free
field can be thought of as holding the metadata of the buf
character array.Creating Redis Strings
A new data type named
sds
is defined in sds.h
to be a synonym for a character pointer:typedef char *sds;
There are two inline function in sds.h, those are sdslen and sdsavail, these two functions are used to get Redis string's length and available space.
static inline size_t sdslen(const sds s) {
struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr))); // get the sds structure address
return sh->len;
}
static inline size_t sdsavail(const sds s) {
struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
return sh->free;
}
sdsnewlen
function defined in sds.c
creates a new Redis String:sds sdsnewlen(const void *init, size_t initlen) {
struct sdshdr *sh;
sh = zmalloc(sizeof(struct sdshdr)+initlen+1);
#ifdef SDS_ABORT_ON_OOM
if (sh == NULL) sdsOomAbort();
#else
if (sh == NULL) return NULL;
#endif
sh->len = initlen;
sh->free = 0;
if (initlen) {
if (init) memcpy(sh->buf, init, initlen);
else memset(sh->buf,0,initlen);
}
sh->buf[initlen] = '\0';
return (char*)sh->buf;
}
Remember a Redis string is a variable of type
struct sdshdr
. But sdsnewlen
returns a character pointer!!
That's a trick and needs some explanation.
Suppose I create a Redis string using
sdsnewlen
like below:sdsnewlen("redis", 5);
// client should call sdsnew function instead of this,
// since sdsnew will calculate the length of the init string for us
// reduce the risk of getting the wrong size
This creates a new variable of type
struct sdshdr
allocating memory for len
and free
fields as well as for the buf
character array.sh = zmalloc(sizeof(struct sdshdr)+initlen+1); // initlen is length of init argument.
After
sdsnewlen
successfully creates a Redis string the result is something like:-----------
|5|0|redis|
-----------
^ ^
sh sh->buf
sdsnewlen
returns sh->buf
to the caller.
What do you do if you need to free the Redis string pointed by
sh
?
You want the pointer
sh
but you only have the pointer sh->buf
.
Can you get the pointer
sh
from sh->buf
?
Yes. Pointer arithmetic. Notice from the above ASCII art that if you subtract the size of two longs from
sh->buf
you get the pointer sh
.
The
sizeof
two longs happens to be the size of struct sdshdr
.
Look at
sdslen
function and see this trick at work:
size_t sdslen(const sds s) {
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
return sh->len;
}
Knowing this trick you could easily go through the rest of the functions in
sds.c
.
The Redis string implementation is hidden behind an interface that accepts only character pointers. The users of Redis strings need not care about how its implemented and treat Redis strings as a character pointer.