好记性不如烂笔头 您所在的位置:网站首页 什么不如烂笔头 好记性不如烂笔头

好记性不如烂笔头

2024-03-14 06:01| 来源: 网络整理| 查看: 265

先介绍三个概念:

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 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



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有