Redisの文字列の実装は、 sds.c の中に書かれています。sdsはSimple Dynamic Stringsの略です。
sds.h の中で宣言されている、 sdshdr 構造体が、Redisの文字列を表現しています。
struct sdshdr {
long len;
long free;
char buf[];
};
buf 文字配列が、実際の文字列を格納しています。
len 変数は、 buf の長さを格納しています。このため、Redisの文字列の長さはO(1)の操作で取得できます。
free 変数はあと、何バイトの文字列が格納できるのかを保持しています。
len も free も、 buf のメタデータを保持していると考えることができます。
sds という名前の新しいデータ型が、 sds.h の中で定義されています。これは文字のポインタの別名です。
typedef char *sds;
新しい文字列を作成する、 sdsnewlen 関数が、 sds.c の中で定義されています。
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;
}
Redisの文字列は sdshdr 構造体の変数ですが、 この関数は文字のポインタを返しています。
これは一種のトリックですが、多様の説明を要するでしょう。
次のようにRedis文字列を作ったとします。
sdsnewlen("redis", 5);
この関数は新しい sdshdr 構造体の変数を作り、 buf と同じようにして、 len 、 free にもメモリを割り当てます。
sh = zmalloc(sizeof(struct sdshdr)+initlen+1); // initlenはinit引き数の長さ
sdsnewlen が成功すると、Redis文字列は次のように作られます。
-----------
|5|0|redis|
-----------
^ ^
sh sh->buf
sdsnewlen は sh->buf を呼び出し元に返します。
sh が指しているRedis文字列を解放したい場合にはどうすればよいでしょうか?
必要なポインタは sh ですが、手元にあるポインタは、 sh->buf です。
sh->buf から、 sh ポインタを取得することができるでしょうか?
はい。ポインタ演算を使うことで求めることができます。上の図を見てお分かりの通り、 sh->buf から、 long 型2つ分のサイズを引くと sh のポインタを得ることができます。
2つの long の大きさは、偶然ですが、 sdshdr の大きさと同じです。
sdslen 関数の中の、このトリックが使われているコードを見てみましょう。
size_t sdslen(const sds s) {
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
return sh->len;
}
このトリックを知っていると、 sds.c の他の関数も、簡単に読むことができるでしょう。
Redis文字列の実装は、文字のポインタのみを受け取るインタフェース関数の裏に隠れています。Redis文字列を使用するだけであれば、どのように実装しているかを気にするひつようはなく、単に文字のポインタとして扱うことができます。