好记性不如烂笔头-字节对齐

先介绍三个概念:

1.自身对齐值:数据类型本身的对齐值,例如char为1 short为2 int、float为4 double为8

2.指定对齐值:编译器默认对齐值或者使用#pragma pack(value)指定的值

3.有效对齐值:取自身对齐值和指定对齐值中较小的那个

对齐规则:

1.不仅结构体的成员变量有有效对齐值,结构体本身也有对齐值,这主要是考虑到结构体的数组,对于结构体或者类,要将其补齐为其有效对齐值的整数倍。结构体的有效对齐值是起最大数据成员的自身对齐值(若有指定对齐值 则取两者小的)。

2.存放结构成员的地址必须是该成员的有效对齐值的整数倍

 

下面来验证一下:


typedef struct  __attribute__ ((__packed__)){
	char a;
	double ab;
	char b;
}no_pack;

typedef struct {
	char a;
	double ab;
	char b;
}default_pack;

#pragma pack(4)
typedef struct{
	char a;
	double ab;
	char b;
}given_pack;

#define show_info(typename, var) printf("%s size = %d, \n a in %p\nab in %p\n b in %p\n", #typename, sizeof(var), &var.a, &var.ab, &var.b)

int main(int argc, char const *argv[])
{
	no_pack no;
	default_pack df;
	given_pack gp;

	show_info(no_pack, no);
	show_info(default_pack, df);
	show_info(given_pack, gp);

	return 0;
}

no_pack结构体是在定义时告诉编译器该结构不需要字节对齐,所以会看到a占一个字节ab占用8个字节b占用1个字节 no_pack的大小为实际占用的内存大小10

default_pack结构体是使用默认的规则进行字节对齐a占用了一个字节之后ab应该要占用8个字节 根据对齐规则2 必须得偏移7个字节之后的地址才是ab对齐值的整数倍 b占用1个字节此时占用内存大小为17字节,但是根绝规则1,结构体需要填充7个字节才能满足其有效对齐值的整数倍的要求,所以default_pack的大小为24

given_pack结构体使用了指定对齐值4,因此成员变量的地址要是4的倍数a占用一个字节之后 根据对齐规则2必须得偏移3个字节之后才能满足ab变量放置条件,接着b占用1个字节此时占用内存大小为13字节,根绝规则1,需要填充3个字节,大小为16

思考一下这么做会带来什么问题?

聪明的你一定想到,字节对齐会造成浪费,同样的数据成员 default_pack比no_pack每个变量要多占用14byte的空间,而且是浪费掉,特别在系统长时间运行,业务量很大的时候,会造成很大的内存浪费耶。所以为什么需要字节对齐?

字节对齐的意义

"各个硬件平台对存储空间的处理上有很大的不同,一些平台对某些特定类型的数据只能从某些特定地址开始存取。比如有些架构的CPU在访问 一个没有进行对齐的变量的时候会发生错误,那么在这种架构下编程必须保证字节对齐.其他平台可能没有这种情况,但是最常见的是如果不按照适合其平台要求对 数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为32位系统)如果存放在偶地址开始的地方,那么一个读周期就可以读出这32bit,而如果存放在奇地址开始的地方,就需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该32bit数据。"

由此可知,因为硬件平台的差异,字节对齐在某些硬件平台上可以让cpu更加高效。所以字节对齐是一种牺牲内存换取cpu效率的无奈之举。

那么我们应该怎么做?

1. 在定义结构体或者联合的时候,尽量把自身对齐值较大的先定义,比如default_pack先定义成员变量ab再分别定义a、b此时结构体的大小为16,又省了8byte...

2.尝试使用pack自定义对齐值大小  一般定义为结构体使用较多的成员类型的大小

=======================================================================================================================================

可变长结构体

what,可变长结构体是啥?结构体还需要可变长吗?成员变量定义一个指针,通过malloc想要多长就多长呀 还讨论可变长结构体有什么必要?

答案是当然有必要,对于极简主义来说,指针也占用内存啊

可变长结构的优点:

1. 不需要初始化,数组名就是偏移的地址

2.不占用任何空间

可变长结构的缺点:

当数据区大小发生改变时,需要重新分配一块更大的内存整块移动(realloc)

定义的方式:

//普通定义
struct stringbuffer{
    unsigned int len;
    unsigned int valid;
    char *buf;
};

//可变长结构体的定义
struct stringbuffer{
    unsigned int len;
    unsigned int valid;
    char buf[];
};

*你会发现 普通定义的结构体比可变长结构体大小多了一个指针的大小

由此延伸到redis的sds.h的源码分析

redis底层定义了五个数据结构最基础的结构用于存储,每个结构体都包含一个buf

初始化时:

sds sdsnewlen(const void *init, size_t initlen) {
    void *sh;
    sds s;
    char type = sdsReqType(initlen);
    //默认使用sds_type_8
    if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;
    int hdrlen = sdsHdrSize(type);
    unsigned char *fp; /* flags pointer. */
    sh = s_malloc(hdrlen+initlen+1);//指向结构体地址
    if (init==SDS_NOINIT)
        init = NULL;
    else if (!init)
        memset(sh, 0, hdrlen+initlen+1);
    if (sh == NULL) return NULL;
    s = (char*)sh+hdrlen;//指向结构体的buf指针
    fp = ((unsigned char*)s)-1;//指向结构体flag的地址
    //根据不同类型的结构体初始化成员
    switch(type) {
        case SDS_TYPE_5: {
            *fp = type | (initlen << SDS_TYPE_BITS);
            break;
        }
        case SDS_TYPE_8: {
            SDS_HDR_VAR(8,s);
            sh->len = initlen;
            sh->alloc = initlen;
            *fp = type;
            break;
        }
        case SDS_TYPE_16: {
            SDS_HDR_VAR(16,s);
            sh->len = initlen;
            sh->alloc = initlen;
            *fp = type;
            break;
        }
        case SDS_TYPE_32: {
            SDS_HDR_VAR(32,s);
            sh->len = initlen;
            sh->alloc = initlen;
            *fp = type;
            break;
        }
        case SDS_TYPE_64: {
            SDS_HDR_VAR(64,s);
            sh->len = initlen;
            sh->alloc = initlen;
            *fp = type;
            break;
        }
    }
    if (initlen && init)
        memcpy(s, init, initlen);
    s[initlen] = '\0';
    return s;
}

sds扩容时操作:

sds sdsMakeRoomFor(sds s, size_t addlen) {
    void *sh, *newsh;
    //剩余可用长度
    size_t avail = sdsavail(s);
    size_t len, newlen;
    //s[-1]比较隐晦
    char type, oldtype = s[-1] & SDS_TYPE_MASK;
    int hdrlen;

    //如果剩余可用长度还能满足 直接返回 不需要扩容
    if (avail >= addlen) return s;
    //s申请的长度
    len = sdslen(s);
    //计算指向结构体首地址
    sh = (char*)s-sdsHdrSize(oldtype);
    //新的长度
    newlen = (len+addlen);
    //如果新长度小于阀值1024*1024则成倍增长 
    if (newlen < SDS_M*AX_PREALLOC)
        newlen *= 2;
    else
        newlen += SDS_MAX_PREALLOC;
    //获取到新的长度的结构应该使用什么类型的结构体
    type = sdsReqType(newlen);

    /* Don't use type 5: the user is appending to the string and type 5 is
     * not able to remember empty space, so sdsMakeRoomFor() must be called
     * at every appending operation. */
    if (type == SDS_TYPE_5) type = SDS_TYPE_8;
    
    hdrlen = sdsHdrSize(type);
    //如果需要的长度当前结构体仍能满足 则进行realloc操作
    if (oldtype==type) {
        newsh = s_realloc(sh, hdrlen+newlen+1);
        if (newsh == NULL) return NULL;
        s = (char*)newsh+hdrlen;
    } else {
        //否则迁移到满足需求的新结构体
        /* Since the header size changes, need to move the string forward,
         * and can't use realloc */
        newsh = s_malloc(hdrlen+newlen+1);
        if (newsh == NULL) return NULL;
        memcpy((char*)newsh+hdrlen, s, len+1);
        s_free(sh);
        s = (char*)newsh+hdrlen;
        s[-1] = type;
        sdssetlen(s, len);
    }
    sdssetalloc(s, newlen);
    return s;
}

sds[-1]这个操作比较隐晦

sds指向结构体的buf指针,buf是个char类型的指针,因此buf[-1]则是结构体距离buf最近的上一个char变量,也就是flags

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值