简单字符串(SDS)
文章目录
在文件<<sds.c>>和<<sds.h>>中
1. 预备知识
1.1 字节对齐
字节对齐的原因
这篇文章讲的非常彻底,基本仔细阅读完毕后就可以明白。
我这里简单说明一下。字节对齐是怎样对齐的。
- 结构体成员有一个自身对齐值,就是成员本身的大小。
- 编译器自带一个对齐值,我们姑且称为pack值,32位是4, 64位是8。
有效对齐值 = 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位置
其内存结构如下
1.1.2
明白了各个成员的位置分布后,计算sizeof就很简单了,一个结构体成员的大小是其最宽的成员的最小整数倍。
1.2 buf[]使用
明白了内存对齐之后,那么就来看看buf[]的使用
在C语言中char[0]是一种数组的特殊用法,用于标记一个指针在一个结构体最后
:
例如:
typedef struct node
{
int number;//后面的数据长度
char data[0];//这是一个指针,不占空间
}Node;
1.2.1 有什么用?
为了连续内存的分配。
- 如果data是node的成员,那么我们在再给node进行分配内存的时候,需要进行俩次分配内存。这样就会导致number可能和data所指向的内存不连续。
- 但是如果使用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[]不能想用就用
仔细想想。
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
参数说明
- len:buf中的有效长度。
- free:buf中的剩余长度。
- 总长度 = len + free + 1, 1字节用来兼容C字符串。意思是当存储文本数据时,SDS以空白符结束。可以使用strlen,但是存储2进制数据时,则只能使用len。
2.1 优点
- 以O(1)的速度获得len,strlen的复杂度时O(n)
- 记录了字符串长度,可以保证在使用时不会栈溢出
- 当对SDS进行修改之后,会给SDS多分配内存,叫做空间预分配,以来减少分配次数,类似vector的思想。
- 保证二进制安全,可以用来存储图片等数据
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会出错。仔细想想为什么?