Redis源码阅读——SDS
参考Redis设计与实现 以及网上博客阅读Redis源码。
SDS相关知识点见读书笔记。
创建和销毁
为了能够对sds进行相关API的测试,因此把sds模块单独提出来。阅读Redis的Makefile发现,编译sds模块需要的源文件包括sds.c
, sds.h
zmalloc.c
test-sds: sds.c sds.h
$(REDIS_CC) sds.c zmalloc.c -DSDS_TEST_MAIN $(FINAL_LIBS) -o /tmp/sds_test
/tmp/sds_test
但是实际编译后会发现会报很多函数未定义的错。原因是redis源码里面sds的内存分配、释放、重分配这些函数是封装成zmalloc,zfee这些函数的,只单纯的把zmalloc.c提取出来是远远不够的。后面发现redis的作者已经把sds给单独提出来了。包括三个源文件sds.c
,sds.h
,sdsalloc.h
因此执行如下操作即可单独把redis的sds模块提取出来。
提取sds模块
-
新建redis_sds测试目录
选择合适的目录下新建
mkdir redis_sds
-
复制源文件至redis_sds目录下
在redis源码的src目录下执行:
cp sds.c ~/redis_sds/
cp sds.h ~/redis_sds/
cp sdsalloc.h ~/redis_sds/
-
修改sdsalloc.h
复制过来的sdsalloc.h 将sds模块的内存函数封装为使用zmalloc函数。为了简化处理直接使用libc的malloc函数来进行内存管理,同时将zmalloc.h给注释掉。
//#include "zmalloc.h" #define s_malloc malloc #define s_realloc realloc #define s_free free
-
新建主函数
新建主函数sds_test.c
#include <stdio.h> #include <stdlib.h> #include "sds.c" //#include "sds.h" int main(int argc, char *argv[]) { sds s = sdsnew("Hello World!"); printf("Length:%d, Type:%d\n", sdslen(s), sdsReqType(sdslen(s))); s = sdscat(s, "The length of this sentence is greater than 32 bytes"); printf("Length:%d, Type:%d\n", sdslen(s), sdsReqType(sdslen(s))); sdsfree(s); return 0; }
直接include sds.c 即可,因为如果#include “sds.h” 的话,sdsReqType这个函数并没有在sds.h里面声明,而且因为sdsReqType的申明是:
static inline char sdsReqType(size_t string_size) {
有static限制所以不能在sds.h中先声明,所以为了简单就直接#include 了sds.c了 -
编译
为了方便重复编译,所以写了个简单的Makefile。
test : sds_test.c sds.h sds.c sdsalloc.h gcc -o sdstest sds_test.c
只需要编译sds_test.c 即可。因为sds_test.c 里面是直接#include sds.c 了所以再
gcc -o sdstest sds_test.c sds.c
会将sds.c 里面的函数重复编译两次,造成Multiple definition 问题。之后只需要执行make命令就可以生成可执行文件sdstest。
执行后输出为:
./sdstest Length:12, Type:0 Length:64, Type:1
sds的创建
通过
sdsnew
来创建了一个sds。sdsnew源码为:/* Create a new sds string starting from a null terminated C string. */ sds sdsnew(const char *init) { //使用?条件判断符来简化if语句对NULL的判断,直接使用strlen来返回字符指针的长度。 size_t initlen = (init == NULL) ? 0 : strlen(init); return sdsnewlen(init, initlen); }
需要注意的是字符数组和字符指针是有区别的:字符指针的数据是存放在进程的虚拟地址空间的程序代码和数据段,是只读的不能修改。字符数组存放的字符串数据是存放在用户栈的,是可以更改的。且字符指针的数据没有"\0"这个结束符。
参考博客讲的很好:https://blog.csdn.net/on_1y/article/details/13030439
sdsnew
通过把字符串长度和字符串传递给sdsnewlen
,来完成创建。/* Create a new sds string with the content specified by the 'init' pointer * and 'initlen'. * If NULL is used for 'init' the string is initialized with zero bytes. * * The string is always null-termined (all the sds strings are, always) so * even if you create an sds string with: * * mystring = n("abc",3); * * You can print the string with printf() as there is an implicit \0 at the * end of the string. However the string is binary safe and can contain * \0 characters in the middle, as the length is stored in the sds header. */ sds sdsnewlen(const