为结构体分配额外存储空间的方法

来源:互联网 发布:gta5女角色捏脸数据 编辑:程序博客网 时间:2024/05/16 23:52
本人是菜鸟一枚,在网络上多位技术大牛的指导下,尤其是读了黄建健宏老师的《Redis设计与实现》和黄老师注释的Redis源码,对Redis有了一些理解,在此深表感谢。当然更应感谢Redis开源项目的作者。
言归正传,在此介绍自己的一份收获。在Redis的sds.h和sds.c文件下,有sdshdr结构体的定义和相关的函数实现,使sdshdr成为一种具备多种优良特性的符串型数据结构,相关源码如下:
struct sdshdr {
    
    // buf 中已占用空间的长度
    int len;




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




    // 数据空间
    char buf[];
};
//创建一个由init和initlen初始化的sdshdr型变量
sds sdsnewlen(const void *init, size_t initlen) {
    struct sdshdr *sh;
    // 根据是否有初始化内容,选择适当的内存分配方式
    // T = O(N)
    if (init) {
        // zmalloc 不初始化所分配的内存
        sh = zmalloc(sizeof(struct sdshdr)+initlen+1);
    } else {
        // zcalloc 将分配的内存全部初始化为 0
        sh = zcalloc(sizeof(struct sdshdr)+initlen+1);
    }
    // 内存分配失败,返回
    if (sh == NULL) return NULL;
    // 设置初始化长度
    sh->len = initlen;
    // 新 sds 不预留任何空间
    sh->free = 0;
    // 如果有指定初始化内容,将它们复制到 sdshdr 的 buf 中
    // T = O(N)
    if (initlen && init)
        memcpy(sh->buf, init, initlen);
    // 以 \0 结尾
    sh->buf[initlen] = '\0';
    // 返回 buf 部分,而不是整个 sdshdr
    return (char*)sh->buf;
}
从以上源码可以看出,sdshdr存储字符串的存储区并不在结构体内,是在创建时根据实际需要申请的,实际存储initlen个字符,为新建的sdshdr变量分配的存储空间大小是(sizeof(struct sdshdr)+initlen+1)字节,这样刚好满足要求。没有浪费内存。在其他情况下,怎样处理呢?请看下面的结构体:
typedef struct xRecord
{
double m_v0;
short m_v1;
char m_buff[];
}Record,*PRecord;
如果要采用类似创建sdshdr变量的方法为Record变量动态分配存储空间,应该分配多少空间呢?
PRecord CreateRecord(const double v0,const short v1,const char buff[])
{
size_t buff_size=strlen(buff)+1;
size_t extra_size=?;//extra_size,在Record结构体外分配的存储空间大小;
size_t sum_size=sizeof(Record)+extra_size;//sum_size代表需要的存储空间总量;
PRecord pr=(PRecord)malloc(sum_size);//为了不转移主题,在此假设能正确分配动态存储空间,暂不检查malloc()函数的结果;
pr->m_v0=v0;
pr->m_v1=v1;
memcpy(pr->m_buff,buff,buff_size);
return pr;
}
其中的extra_size应该怎样确定呢?如果照搬sdshdr的分配方法,即:
extra_size=buff_size;
这样,由于结构体内存圆整,在结构Record内会有一部分填充空间(在32位环境下是2字节)未被充分利用。为了充分利用这部分填充空间,需要确定
extra_size。也就是在利用Record的填充空间的前提下,还需要多少存储空间。
size_t ExtraSize(const size_t need_size)
{//need_size,理论上结构体外需要的存储空间大小;
Record rc;//定义这个变量rc而不是人为计算Record结构体的内存分布,是为了充分利用系统功能,保证可移植性;
size_t spare_size=(size_t)((char*)(&rc+1)-rc.m_buff);//spare_size,Record结构内m_buff之后填充空间的大小;
size_t extra_size=(spare_size>=need_size?0:(need_size-spare_size));//extra_size,实际上需要在结构体外分配的存储空间大小;
return extra_size; 
}
完整的CreateRecord()函数实现如下:
PRecord CreateRecord(const double v0,const short v1,const char buff[])
{
size_t buff_size=strlen(buff)+1;
size_t extra_size=ExtraSize(buff_size);//extra_size,在Record结构体外分配的存储空间;
size_t sum_size=sizeof(Record)+extra_size;//sum_size代表需要的存储空间总量;
PRecord pr=(PRecord)malloc(sum_size);
if(pr!=NULL)
{
pr->m_v0=v0;
pr->m_v1=v1;
memcpy(pr->m_buff,buff,buff_size);
}
return pr;

}


0 0
原创粉丝点击