柔性数组(Redis源码学习)

柔性数组(Redis源码学习)柔性数组(Redis源码学习) 1. 问题背景 在阅读Redis源码中的字符串有如下结构,在sizeof(struct sdshdr)得到结果为8,在后续内存申请和计算中也用到。其实在工作中有遇到过这

柔性数组(Redis源码学习)

柔性数组(Redis源码学习)

1. 问题背景

在阅读Redis源码中的字符串有如下结构,在sizeof(struct sdshdr)得到结果为8,在后续内存申请和计算中也用到。其实在工作中有遇到过这种 struct结构 + 应用数据的情况,但没有意识到自己使用的是柔性数组,在学习阅读Redis代码中,遇到该方法,就特总结记录之。

/* * 类型别名,用于指向 sdshdr 的 buf 属性 */
typedef char * sds;
/* * 保存字符串对象的结构 */
struct sdshdr {    
    // buf 中已占用空间的长度
    int len;
    // buf 中剩余可用空间的长度
    int free;
    // 数据空间
    char buf[];
};

2. 柔性数组

柔性数组(flexible array member)也叫伸缩性数组成员,这种结构产生与对动态结构体的去求。在日常编程中,有时需要在结构体中存放一个长度是动态的字符串(也可能是其他数据类型)。

一般的做法,是在结构体中定义一个指针成员,这个指针成员指向该字符串所在的动态内存空间。在通常情况下,如果想要高效的利用内存,那么在结构体内部定义静态的数组是非常浪费的行为。其实柔性数组的想法和动态数组的想法是一样的。

柔性数组用来在结构体中存放一个长度动态的字符串。
本文基于redis 的sds.c源码,进行简单编码验证测试,其实这种柔性数组,在工作中用到过,但是没有意识到这是柔性数组。

上述struct sdshdr结构中,要注意:最后一个变量 buf 数组中,没有长度,这和自己遇到的正常的使用方式不一样,新的知识点
这种用法是C语言中的柔性数组,上面 的sizeof(sdshdr )结果是8,即后面的buf不占空间,只是一个符号,测试上面sdshdr结果如下:

int main(int argc,char **argv){	   
	struct sdshdr t;	  
    
    printf("int len:%d
",sizeof(int));	
	printf("sdshdr len:%d
",sizeof(struct sdshdr));
	  
    printf("Address:
");
    printf("t	 %p
", &t);
    printf("t.len	 %p
", &(t.len));
    printf("t.free	 %p
", &(t.free));	
    printf("t.buf	 %p
", &(t.buf));	
	return 0;	
}

RHEL6.9上执行上面代码块得到结果如下:

$ ./sdshdr                  
int len:4
sdshdr len:8
Address:
t        0x7fff9572fa50
t.len    0x7fff9572fa50
t.free   0x7fff9572fa54
t.buf    0x7fff9572fa58

可以看到 t.buf 是该结构的最后的地址,是最后一个点,简单图示如下:

image

如果后续再malloc相关的内存,则就会在t.buf后面连续,简单编写代码进行验证。要加入对应的sds.h文件,或者直接将结构定义在main函数之前。

int main(int argc,char **argv){	   
	struct sdshdr t;	  
	  
	printf("int len:%d
",sizeof(int));	
	printf("sdshdr len:%d
",sizeof(struct sdshdr));
	  
    printf("Address:
");
    printf("t	 %p
", &t);
    printf("t.len	 %p
", &(t.len));
    printf("t.free	 %p
", &(t.free));	
    printf("t.buf	 %p
", &(t.buf));	
    
    printf("sizeof(char):	 %d
", sizeof(char));	
    struct sdshdr *p=(struct sdshdr*)malloc(sizeof(struct sdshdr) + sizeof(char)*8);
    printf("After malloc the struct"s size is %d
",sizeof(struct sdshdr));

    printf("Address:
");
    printf("p	 %p
", p);
    printf("p->len	 %p
", &(p->len));
    printf("p->free	 %p
", &(p->free));	
    printf("p->buf	 %p,sizeof(p):%d
", &(p->buf),sizeof(p));	
    
    memset(p,0,sizeof(struct sdshdr) + sizeof(char)*8);
    char *str="Hello";
    memcpy(p->buf,str,strlen(str));
    printf("p->buf:%s
",p->buf);
    
    char *str1="HelloWorldttttttt";
    memcpy(p->buf,str1,sizeof(char)*8-1);
    printf("p->buf:%s
",p->buf);
    printf("strlen(p->buf):%d
",strlen(p->buf));
	  return 0;	
}

上述代码进行编译,获得可执行文件,执行结果如下:

$ ./sdshdr                  
int len:4
sdshdr len:8
Address:
t        0x7ffea0a8c420
t.len    0x7ffea0a8c420
t.free   0x7ffea0a8c424
t.buf    0x7ffea0a8c428
sizeof(char):    1
After malloc the struct"s size is 8
Address:
p        0x1bc3010
p->len   0x1bc3010
p->free  0x1bc3014
p->buf   0x1bc3018,sizeof(p):8
p->buf:Hello
p->buf:HelloWo
strlen(p->buf):7
$


## 3. 使用方法
从C99开始便支持了不完整类型实现柔性数组成员。为什么使用不完整类型呢?
```C language
int a[] = {10};

看到这个声明语句,我们发现a[]其实就是个数组记号,不完整类型,由于赋值语句,所以在编译时便确定了数组的大小,是一个完整的数组类型。
在结构体中便利用不完整类型在运行对动态的数组进行指明。
C99标准的定义如下:

struct Test{
    int a;
    char p[]; // 不只是char类型,其他类型同样也是可以
}

由于声明内存连续性的关系,柔性数组成员必须定义在结构体的最后一个,并且不能是唯一的成员。
我们再来看一看整个结构体(包含数组内存的分布情况),进行简单编码验证。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct Test
{
    int a;
    char p[];
} Test;
int main()
{
    Test *t=(Test*)malloc(sizeof(Test)+sizeof(char)*(10+1));
    printf("sizeof(int):%d,sizeof(Test):%d
",sizeof(int),sizeof(Test));
    strcpy(t->p,"hello");
    printf("t->p:%s
", (t->p));
    printf("Address:
");
    printf("t	 %p
", t);
    printf("t.a	 %p
", &(t->a));
    printf("t.p	 %p
", (t->p));    
    free(t);    //只需要释放一次内存
    return 0;
}

在linux上的执行结果如下:

$ ./sdshdr                  
sizeof(int):4,sizeof(Test):4
t->p:hello
Address:
t        0x7e0010
t.a      0x7e0010
t.p      0x7e0014

4. 小结

  1. 在结构体中存放一个长度是动态数据类型时,可以考虑到柔性数组。
  2. 一般做法,是在结构体中定义一个指针成员,这个指针成员指向所在的动态内存空间。
  3. 该指针成员,不占结构体空间,只是一个符号。
  4. 柔性数组成员必须定义在结构体的最后一个,并且不能是唯一的成员。

5. 参考文献

https://www.cnblogs.com/davygeek/p/5748852.html

https://blog.csdn.net/qq_40477151/article/details/78905567

https://www.cnblogs.com/pluviophile/p/7571410.html

本人才疏学浅,参考网络文章及代码验证,如有错误不当之处,请批评指正,如有侵权,请立即联系我进行删除。

如果能为您带来一点点帮助,那将是我的荣幸,多谢您关注和转发推荐,谢谢!

image

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
转载请注明出处: https://daima100.com/6064.html

(0)
上一篇 2023-04-18 10:30
下一篇 2023-04-18 11:30

相关推荐

  • 使用apt-get安装vim

    使用apt-get安装vim在Linux环境下,我们经常需要使用命令行界面进行操作。而vim是一款非常经典的命令行文本编辑器,在Linux系统上得到广泛使用。在本篇文章中,我们将会介绍如何使用apt-get命令来安装vim。

    2024-07-26
    41
  • plsql实例_sql项目实例

    plsql实例_sql项目实例第六章 项目案例1 需求: 1. 建立一个银行账户的表bank_account,具备以下字段和约束 2. 完成该表基本CRUD 3. 模拟转账流程 4. 模拟异常之后的业务回滚 开发文档: 1. 按需

    2023-02-04
    146
  • 搞懂 Redis 持久化,RDB模式AOF模式工作原理详解及操作

    搞懂 Redis 持久化,RDB模式AOF模式工作原理详解及操作搞懂 Redis 持久化,RDB模式AOF模式工作原理详解及操作

    2023-04-05
    153
  • 在Ubuntu 20.04上安装GCC以便进行Python编译

    在Ubuntu 20.04上安装GCC以便进行Python编译在Ubuntu操作系统上编译Python应用程序时,GCC编译器是必不可少的工具。这篇文章将详细介绍在Ubuntu 20.04上安装GCC用于Python编译的过程。GCC是一种开源编译器,可以通过Ubuntu的软件包管理器进行安装。通过以下步骤可以在Ubuntu 20.04上安装GCC。

    2024-01-09
    102
  • 用Python创建Dataframe

    用Python创建Dataframe在数据科学领域,经常需要对复杂且规模庞大的数据进行处理和分析。对于这些数据,最常用的方式就是将其组织成表格或矩阵的形式。在Python中,可以使用Pandas库来创建和处理这些表格型数据。而这里要介绍的是如何使用Python创建Dataframe。

    2024-09-11
    27
  • Python切片的用法

    Python切片的用法切片是指对于某个序列,可以通过一定的方式选择其中的一部分,并返回一个新的序列。切片的操作是非常常用且高效的,特别是在数据处理和处理大数据量时。Python提供了很简单的方法对序列进行切片。

    2024-04-09
    72
  • Python手动结束线程

    Python手动结束线程多线程是并行编程的重要组成部分,Python也提供了方便的多线程编程功能。在多线程编程中,线程的结束是一个重要的问题,如果不正确地结束线程,会导致程序出现意外错误。本文将详细介绍Python手动结束线程的方法,为读者提供参考。

    2024-07-02
    43
  • 开启python的曼妙之旅(python全程)

    开启python的曼妙之旅(python全程)安装完python之后,我们可以做两件事情,

    2023-11-25
    152

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注