您的位置:首页 > 编程语言 > C语言/C++

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

2014-02-13 00:17 477 查看

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

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/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息