失落的C语言结构体封装艺术

写在最前面: 这里的文章并非原文,是我自己的观后小结,所以简短并会省略一些内容。


1.对齐和填充

在现代处理器上,你的C编译器在内存里对基本的C数据类型的存放方式是受约束的,为的是内存访问更快。

 

在x86或ARM处理器上,基本的C数据类型的存储一般并不是起始于内存中的任意字节地址。除了字符外,其他每种类型都有对齐要求。

类型

起始地址

字符

任意

2字节短整型

起始于偶地址

4字节整型

起始于被4整除的地址

8字节长整型

起始于被8整除的地址

双精度浮点型

起始于被8整除的地址

浮点型

起始于被4整除的地址

 

带符号与不带符号间没有差别。这个行话叫:在x86和ARM上,基本C语言的类型是自对齐(self-aligned).

 

数据与数据间。由于受他们起始位置的影响,数据与数据间存放的地址会有间隙,我们把这无用的地址(间隙)称为“水坑”。

 

 

2.结构体对,填充和重构

总的来说,一个结构体实例会按照它最宽的标量成员对齐。编译器这样做,把它作为最简单的方式来保证所有成员是自对齐,为了快速访问的目的。
 
在C语言里,结构体的地址与它第一个成员的地址是相同的——没有前置填充。注意:在C++里,看上去像结构体的类可能不遵守这个规则!(遵不遵守依赖于基类和虚拟内存函数如何实现,而且因编译器而不同。)
 
不但结构体内会存在自对齐和填充,结构体和结构体之间也会存在尾随填充(trailingpadding)和跨步地址(stride address)。这些都会造成结构体在内存中的浪费,因此重构结构体很重要,这也是结构体封装的艺术。
 
第一件需要注意的事情是,“水坑”仅发生于两个地方。一个是大数据类型(有更严格的对齐要求)的存储区域紧跟在一个较小的数据类型的存储区域之后。另一个是结构体自然结束于它的跨步地址之前,需要填充,以使下一个实例可以正确对齐。
 

消除“水坑”的最简单的方法是按对齐的降序来对结构体成员重排序。就是说:所有指针对齐的子域在前面,因为在64位的机器上,它们会有8字节。接下来是4字节的整型;然后是2字节的短整型;然后是字符域。



3.重构时的困难

(1)难以处理的标量问题:数据类型大小的不确定性。(可用sizeof()来检查存储大小)

 

(2)可读性和缓存局部性:你应该做的事情是保持可读性——把相关的和同时访问的数据组合到毗邻的区域——这也会提高缓存行的局部性。这都是用代码的数据访问模式的意识,聪明地重排序的原因。

如果你的代码有多线程并发访问一个结构体,就会有第三个问题:缓存行反弹(cache line bouncing)。为了减少代价高昂的总线通信,你应该组织你的数据,使得在紧凑的循环中,从一条缓存行中读取,而在另一条缓存行中写。

是的,这与之前关于把相关数据组成同样大小的缓存行块的指南有些矛盾。多线程是困难的。缓存行反弹以及其它的多线程优化问题是十分高级的话题,需要整篇关于它们的教程。这里我能做的最好的就就是让你意识到这些问题的存在。


4. 转载资料

原文链接:  Eric S. Raymond    翻译:  伯乐在线  cjpan
译文链接:  http://blog.jobbole.com/57822/



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值