zoukankan      html  css  js  c++  java
  • 柔性数组(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

  • 相关阅读:
    “智商平平”学软件
    一个多线程示例程序的BUG修复
    金旭亮《C#面向对象程序设计》2011完整版发布
    《.NET 4.0网络开发入门之旅》7:填平缓冲区陷阱
    《.NET 4.0网络开发入门之旅》5:与Socket的“再次见面”
    《.NET 4.0网络开发入门之旅》6:“麻烦”的数据缓冲区
    软件天才与技术民工
    .NET 4.0 技术亮点剖析——在中科院计算所的讲座PPT及源码下载
    C#实现程序的开机启动
    C#获取当前时间、日期
  • 原文地址:https://www.cnblogs.com/love-avrlinux/p/14958509.html
Copyright © 2011-2022 走看看