[SDS阅读理解/1]源码中的宏

这节记录下对源码中的几个宏的理解

/* sds.h */

36 #define SDS_MAX_PREALLOC (1024*1024)

75 #define SDS_TYPE_5  0
76 #define SDS_TYPE_8  1
77 #define SDS_TYPE_16 2
78 #define SDS_TYPE_32 3
79 #define SDS_TYPE_64 4
80 #define SDS_TYPE_MASK 7
81 #define SDS_TYPE_BITS 3
82 #define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (void*)((s)-(sizeof(struct sdshdr##T)));
83 #define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))))
84 #define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS)

       以上就是源码中所定义的宏,在文件中对应行号可以找到。C语言中的宏挺强大的,当然,只有熟练使用后才能体会到,我对C中宏的感觉就是一种朦胧美…具体美在哪,还需要继续学习:)。


       首先是第一个宏SDS_MAX_PREALLOC,全称是Simple Dynamic String Maximum Previous Allocated,专业英语的人看起来可能不像一个句子,我觉得还行,大概意思就是预先申请的最大内存字节数,1024 * 1024这么多个字节也就是1MB(我们平时分配内存的时候一般是用malloc()这个函数,你要多少个字节,它就给你申请多少个字节的内存,如果空间足够的话)。宏的用法也是C语言中的一个基础知识,这里就不详述了。两个地方用到了这个宏,在.c文件中210、213行,也就是sdsMakeRoomFor(sds, size_t)这个函数里头,这里先不讲函数内部的逻辑实现,简单说明下为什么需要这个宏。在官方说明文档接近末尾的地方有一句-
Since SDS tries to be efficient it can’t afford to reallocate the string every time new data is appended, since this would be very inefficient, so it uses preallocation of some free space every time you enlarge the string.
大概意思就是,SDS为了提高效率,每一次的内存分配,都会多申请一定的空闲空间,避免下次使用这个字符串变量的时候空间不够大然后又重新分配内存(申请内存是需要开销的,为了提高效率,就少申请咯,当然,现在我们的硬件条件提升了这么多,一次多申请点内存完全不是事)。看下面这几行代码-

210 if (newlen < SDS_MAX_PREALLOC)
211     newlen *= 2;
212 else
213     newlen += SDS_MAX_PREALLOC;

以上代码可在sdsMakeRoomFor(sds, size_t)函数对应行数中找到。这个函数顾名思义就是为sds字符变量增大内存空间,那个newlen就是sds变量已有的长度加上需要增加的长度的和(看源码就清楚了)。代码的意思就是如果newlen < SDS_MAX_PREALLOCnewlen就扩大为它本身的两倍,反之就是在基础上增加SDS_MAX_PREALLOC。好吧,这几句话很幼稚…为什么要这样做?就是上面说明的,为了减少内存申请的次数,提高效率嘛!假设我们已经有一个字符串变量存了一串字符,而且空间满了,但是我们又需要在其后面添加新的字符,这时我们就可以调用上面提到的函数,告诉它我们要为这个字符串变量增加size个空间,这个函数怎么做呢?它就是根据上面几行代码决定最后这个变量的空间是多大。好了,废话说完了,这个SDS_MAX_PREALLOC宏其实并不复杂,我觉得就是为了限制我们申请一个变态大的内存空间或是在一个死循环里头不断申请内存空间,导致最后可用空间都被占完了,程序崩溃,电脑死机…


       下面讲解下#define SDS_TYPE_*这几个宏。现在的系统一般都是32位或64的,SDS作者还考虑到5位、8位、16位的,原谅我没用过这些位数的系统…前5个宏就是用来标识不同位数的系统的。为什么要标识?因为不同位数的系统,它所能储存的最大字符个数不一样(我不知道这个说法正不正确…)。我上网查找了下,http://bbs.csdn.net/topics/390837000贴里#3楼是这样说的
截图
源码里是通过定义一个sds变量并初始化时的初始化大小来判断是使用哪个宏(上面说的5个中的一个),然后通过这个宏来判断并计算sds变量前的那个头结构体的大小,这里还没介绍到那个结构体,先不具体说了。我们只要知道这5个宏是用来标识不同位数系统的就可以了。作者的目的应该是为了实现通用性吧,就是不同位数系统都可以用这套API。
       宏SDS_TYPE_MASK是用来让前5个宏与其做按位与运算的,把0~4换成二进制和7的二进制进行按位与运算,发现还是十进制的0~4。这里我有点纳闷,为啥不直接使用,而是要按位与后再使用呢…难道是因为头结构体里头储存标识的是用unsigned char类型的变量,而如果用int类型的变量又会遇到不同位数系统int所占的字节数不同。因此为了避免这个问题而使用的一种方法吗?还不确定大神为啥要这样用,以后弄清楚后再来完善。
       宏SDS_TYPE_BITS在源码里用的比较少,只是与第一个宏有关,而且作者在.h文件也说了
/* Note: sdshdr5 is never used, we just access the flags byte directly.
* However is here to document the layout of type 5 SDS strings. */
所以这里不管它,毕竟不重要。


       下面重点讲下这个宏SDS_HDR_VAR(T,s),灰常强大,必须重点讲。它有两个参数,第一个T,可填8\16\32\64(从源码中可看到)。第二个s,就是一个sds变量。看下面这行代码-

struct sdshdr##T *sh = (void*)((s)-(sizeof(struct sdshdr##T)));

这是宏还未展开前的代码,其实就是根据两个参数,定义并初始化一个相应类型的结构体指针变量,它指向一个已存在的这个类型的结构体内存。如何实现的呢?先看左边,两个##的作用是在宏展开后变成sdshdr8sdshdr16等,如果没有两个##,展开后就还是sdshdrT,这不是我们想要的结果。重要的还是右边,通过一个已存在的sds变量的首地址减去这个结构体本身的所占内存的字节大小来得到该结构体的首地址(因为头结构体和sds变量的内存的地址是连续的,作者文档里有说,因此我们可以通过指针偏移来得到想要的地址)。得到该地址是有用处的,这里先不说。似乎用函数是无法实现该功能的吧,得好好学学这种宏用法。


       宏SDS_HDR(T,s)和上面的差不多,参数也一样,只不过它没有定义指针,而是直接获取对应结构体的首地址。作用这里也先不说。


       最后一个宏SDS_TYPE_5_LEN(f),源码里展开后其实就是0 >> 3,0右移多少位都是0,还是不懂为什么要这样用。不多说了。


       好了,这节就记录到这里了!:)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值