Redis源码学习-1-简单字符串

简单字符串(SDS)


在文件<<sds.c>>和<<sds.h>>中

1. 预备知识

1.1 字节对齐

字节对齐的原因
这篇文章讲的非常彻底,基本仔细阅读完毕后就可以明白。


我这里简单说明一下。字节对齐是怎样对齐的。

  1. 结构体成员有一个自身对齐值,就是成员本身的大小。
  2. 编译器自带一个对齐值,我们姑且称为pack值,32位是4, 64位是8。
  3. 有效对齐值 = min{自身对齐值, pack值}

有效对齐值N就是表示数据要存放在N的倍数的地址上。

1.1.1 影响

影响sizeof的大小
64为编译器值。

struct A{
    char a;
    short b;
    int c;
    double d;
};

struct B{
    char a;
    double b;
    short c;
    double d;
};
sizeof A = 16;
sizeof B = 32;

简单分析下B, &B表示B结构体对象的首地址。
a的有效对齐值为1, 所以占据&B + 00位置
b的有效对齐值为8, 所以占据&B + 08位置
c的有效对齐值为2, 所以占据&B + 0A位置
d的有效对齐值为8, 所以占据&B + 12位置

其内存结构如下
Resource/p1133)]

1.1.2

明白了各个成员的位置分布后,计算sizeof就很简单了,一个结构体成员的大小是其最宽的成员的最小整数倍。

1.2 buf[]使用

明白了内存对齐之后,那么就来看看buf[]的使用

在C语言中char[0]是一种数组的特殊用法,用于标记一个指针在一个结构体最后
例如:

typedef struct node
{
int number;//后面的数据长度
char data[0];//这是一个指针,不占空间
}Node;

更多资料请看这里

1.2.1 有什么用?

为了连续内存的分配。

  1. 如果data是node的成员,那么我们在再给node进行分配内存的时候,需要进行俩次分配内存。这样就会导致number可能和data所指向的内存不连续。
  2. 但是如果使用data[0]这种形式。我们可以这样做
Node obj = (Node*)malloc(sizeof(Node) + lenData);

而data成员是指向结构体后的下一字节地址,所以正好指向lenData那段长度。

1.2.2 正确例子
typedef struct node{
    int number;
    char data[];
}Node;

int main()
{
    printf("sizeof Node = %d\n", sizeof(Node));
    Node *obj = (Node*)malloc(sizeof(Node) + 16);
    obj->data[0] = 3;
    obj->data[1] = 3;
    obj->data[2] = 3;
    char * scan = (char*)obj;
    scan += 4;
    printf("data[0] = %d\n", *scan);
    printf("data[0] = %d\n", *(scan + 1));
    printf("data[0] = %d\n", *(scan + 2));
}
char[]不能想用就用

char[]不能想用就用

仔细想想。

typedef struct node{
    int number;
    char trivial;
    char data[];
}Node;

Node结构体的大小是8,而data指向的却是第6个字节。

一句话,就是char[]有指向padding字节的风险。
P就是padding填充的字节。
在这里插入图片描述

2. 数据结构

struct sdshdr {
    
    // buf 中已占用空间的长度
    int len;

    // buf 中剩余可用空间的长度
    int free;

    // 数据空间
    char buf[];
};

现在我们再来看这里的数据结构,就很明了了。
sizeof sdshdr = 8

参数说明

  1. len:buf中的有效长度。
  2. free:buf中的剩余长度。
  3. 总长度 = len + free + 1, 1字节用来兼容C字符串。意思是当存储文本数据时,SDS以空白符结束。可以使用strlen,但是存储2进制数据时,则只能使用len。

2.1 优点

  1. 以O(1)的速度获得len,strlen的复杂度时O(n)
  2. 记录了字符串长度,可以保证在使用时不会栈溢出
  3. 当对SDS进行修改之后,会给SDS多分配内存,叫做空间预分配,以来减少分配次数,类似vector的思想。
  4. 保证二进制安全,可以用来存储图片等数据

3 SDS API

3.1 简单分析 sdslen

typedef char *sds;
static inline size_t sdslen(const sds s) {
    struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
    return sh->len;
}

第一眼看到这里实际上是有一点点懵的。但是如果你明白了预备知识,知道了char[]的作用,你就知道这里实际上先计算得到了结构体的首地址。然后返回其长度len。

3.2 如何兼容C字符串函数

Redis使用了一套巧妙的机制来兼容C字符串函数。

SDS的一系列API并不返回sdshdr结构体,而是返回SDS,即cha *,因此类型上看,我们完全将SDS看做C原因言的字符串。但是如果从SDS API的角度去看,每次在API中就会由SDS计算回sdshdr结构体的地址,即可以理解为将SDS转为了sdshdr。

注意,对于一个不是通过SDS API构造的字符串,即普通的C字符串,然后调用SDS API会出错。仔细想想为什么?

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值