免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
1234下一页
最近访问板块 发新帖
查看: 11337 | 回复: 34
打印 上一主题 下一主题

[C] 烦请哪位老大可以介绍一下C语言中的内存对齐的原理和实现? [复制链接]

论坛徽章:
11
摩羯座
日期:2013-09-29 17:39:09白羊座
日期:2014-11-13 09:38:14技术图书徽章
日期:2014-01-17 15:07:36狮子座
日期:2013-12-25 14:01:52技术图书徽章
日期:2013-12-17 11:33:22技术图书徽章
日期:2013-12-03 10:27:57天秤座
日期:2013-11-08 15:47:19申猴
日期:2013-10-29 13:16:32未羊
日期:2013-10-12 22:28:56辰龙
日期:2013-10-09 14:39:5515-16赛季CBA联赛之山东
日期:2016-07-25 10:23:00
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2012-08-27 11:41 |只看该作者 |倒序浏览
求教给位,内存对齐是什么原理,memalign()和posix_memalign()函数一直看不懂,请指教。

论坛徽章:
1
荣誉版主
日期:2011-11-23 16:44:17
2 [报告]
发表于 2012-08-27 23:31 |只看该作者
你指的内存对齐原理是什么意思?
是为什么需要内存对齐吗?

论坛徽章:
11
摩羯座
日期:2013-09-16 11:10:272015亚冠之阿尔萨德
日期:2015-06-12 22:53:29午马
日期:2014-04-15 11:08:53亥猪
日期:2014-03-02 23:46:35申猴
日期:2013-12-06 22:07:00亥猪
日期:2013-11-28 12:03:13双鱼座
日期:2013-11-21 14:43:56亥猪
日期:2013-10-23 10:55:49处女座
日期:2013-10-17 18:15:43午马
日期:2013-09-27 17:40:4215-16赛季CBA联赛之青岛
日期:2016-06-22 00:45:55
3 [报告]
发表于 2012-08-27 23:47 |只看该作者
本帖最后由 Ager 于 2012-08-27 23:47 编辑

对齐是一种策略。

估计楼主是在问这种策略的缘起。

这种策略,打个比方 ——

有家小店的店主怕麻烦,遂贴出告示曰:本店概不受理分币!

那么,这就是“以角币对齐”。

评分

参与人数 1可用积分 -2 收起 理由
lenky0401 -2 不知道你在讲啥。

查看全部评分

论坛徽章:
0
4 [报告]
发表于 2012-08-28 08:47 |只看该作者
1,对齐的实现不算复杂,把起始地址做后向偏移,使得返回的内存空间的起始地址为指定对齐数目的倍数,也因为有这个偏移操作,所以总共使用(或者说加上浪费的)内存会大于或等于实际使用的空间。
2,为什么要做对齐,这主要是从性能上考虑,以X86为例,内存数据加载到CPU cache总是以一条cache line为基本单元,也就是说每一次CPU都会从内存读取64字节的数据到CPU cache,即便我们只使用了其它的一个字节。详情可参考:http://lenky.info/?p=310

论坛徽章:
0
5 [报告]
发表于 2012-08-28 08:49 |只看该作者
当然,有的cpu架构对内存的访问必须是对齐访问,由于了解不多,无法多说。

论坛徽章:
0
6 [报告]
发表于 2012-08-28 09:11 |只看该作者
对齐就是一个class A{char c; int i;};可能不是5字节。

论坛徽章:
1
白羊座
日期:2013-09-18 22:02:26
7 [报告]
发表于 2012-08-28 09:20 |只看该作者
可以自己写个东西,让申请回来的地址实行地址对其:

#define DOUNDARY8(ptr) (((char*)prt) + (0x8 - ((char *)ptr)&0x)

实行8字节对其。

论坛徽章:
11
摩羯座
日期:2013-09-29 17:39:09白羊座
日期:2014-11-13 09:38:14技术图书徽章
日期:2014-01-17 15:07:36狮子座
日期:2013-12-25 14:01:52技术图书徽章
日期:2013-12-17 11:33:22技术图书徽章
日期:2013-12-03 10:27:57天秤座
日期:2013-11-08 15:47:19申猴
日期:2013-10-29 13:16:32未羊
日期:2013-10-12 22:28:56辰龙
日期:2013-10-09 14:39:5515-16赛季CBA联赛之山东
日期:2016-07-25 10:23:00
8 [报告]
发表于 2012-08-28 11:23 |只看该作者
回复 7# file3

受教,多谢楼上几位!

另外问一句楼上file3同学,
会显示成图片中的样子,求庐山真面目~

   

1.jpg (26.08 KB, 下载次数: 60)

1.jpg

论坛徽章:
11
摩羯座
日期:2013-09-16 11:10:272015亚冠之阿尔萨德
日期:2015-06-12 22:53:29午马
日期:2014-04-15 11:08:53亥猪
日期:2014-03-02 23:46:35申猴
日期:2013-12-06 22:07:00亥猪
日期:2013-11-28 12:03:13双鱼座
日期:2013-11-21 14:43:56亥猪
日期:2013-10-23 10:55:49处女座
日期:2013-10-17 18:15:43午马
日期:2013-09-27 17:40:4215-16赛季CBA联赛之青岛
日期:2016-06-22 00:45:55
9 [报告]
发表于 2012-08-28 12:54 |只看该作者
本帖最后由 Ager 于 2012-08-29 03:31 编辑
Ager 发表于 2012-08-27 23:47


lenky0401         -2         不知道你在讲啥。


斑竹大虾,您也忒严厉了吧,就算是我灌水也不至于倒扣2分吧……我在CU上本来就是一个千年穷光蛋,再这麽扣下去,我要成负翁了……呵呵……:)

既然您说“不知道我在讲啥”,那么,我就试着扯扯能让大家感到reasonable的东东吧……(声明一下,我下面纯属“扯”,尽管回帖批判,不要再扣血了……)

“内存对齐”其实算个俗称,比较严格地说法(尤其是在高级编程语言的视阈来看),应该叫“数据结构对齐”。本来嘛,内存都是死的,它自己又不会听口令排队。而“对齐”其实是我们施加在内存上的一套逻辑,一种策略。

之所以有这种策略,除了斑竹您说的,是缘自它是为了满足“性能要求”的必要条件之外,还有一个原因,就是这种策略,也是满足“程序在不同架构之间的‘可移植性’的要求”(某些硬件设施只能在存储器的特定位置处access特定类型的数据或结构)的必要条件之一。

下面综合上述两点,继续扯一下。

首先说一下Cache(特指所谓“CPU高速缓存”)。基本上,Cache在高级语言程序员的视角来看,是透明的。它是完全依赖硬件设施来实现的机制,高级语言一般无法直接干预它,也无法直觉察觉它是如何运行的(不过,可以利用它)。在x86家族中,最早实现Cache机制的,大约是在1985年诞生的80386,但在此前的x86家族成员中,在数据处理的策略上,对齐早就是被固有地运用的了。所以,对齐这个策略,跟Cache并没有直接的渊源关系。不过,在后来的运用中,高级语言编程下的数据结构对齐策略之矢,确是以Cache为的而放的。

再说一下“Cache Line”,它也叫“Cache Block”,即“缓存块”。在32位运算设施上,这个“块”包含了若干个具有连续地址的存储单元,通常,单元长度就是该设施的一个Word长度(或者说“按Word长度对齐” —— 可见,“对齐”的观念早于Cache的观念),即32 bits。不过在x86-32(Intel直接称呼它为IA-32)上,这里的概念有一些混乱。IA-32为了保持对之前的16-bits设施的兼容,“Word”还是16 bits的。x86-64亦然。那么,x86-32上的 Cache Line到底是多长?不管了,对于我们高级语言的程序员来说,关心的就是如何利用Cache。那么 cat /proc/cpuinfo |grep cache 一下就知道了。【—— 关于x86这方面的事情,补充说明与一些概念上的澄清,统统给一个汇总在下面的第16楼里面。】

那么,忽略了架构的区别和Cache不说,就单说“对齐”这件事情。

上面说了,“内存对齐”不妨称作“数据结构对齐”,那么这种策略,实际上是透过把握存储单元在存储器中的位置,即“地址”来实现的,所以,也可以被称作“地址对齐”。而地址这个东西,一些高级语言是可以把握的。下面来扯扯:

CPU在access存储器的时候,有一个“粒度”概念,即CPU在一个操作单位中,只能access一个单位长度的数据,不可再分割,所谓“粒子性”。
  1. Address       Memory           Register
  2. 0              XH       ->        XH
  3. 1              XL       ->        XL
  4. 2              YH       ->        YH
  5. 3              YL       ->        YL
  6. 4              ZH
  7. 5              ZL
  8. 6              OH
  9. 7              OL
复制代码
上图的四个箭头,代表了CPU一次性地在内存中抓取了4个Bytes的数据到寄存器,即这个CPU的“内存access粒度”是4 Bytes的。

如果我们让CPU去抓取X这个数据(由XH+XL两个Bytes组成),那么我们就必须让access这一类数据(比如后来的Y、Z、O)的指针始终保持“从地址0开始的2 Bytes对齐”,即指针的取值只能是0、2、4、6。

否则,如果让指针不这样对齐,比如取值1开始access的话,那么,就会造成这种状况:
  1. Address       Memory           Register
  2. 0              XH                 
  3. 1              XL       ->        XL
  4. 2              YH       ->        YH
  5. 3              YL       ->        YL
  6. 4              ZH       ->        ZH
  7. 5              ZL
  8. 6              OH
  9. 7              OL
复制代码
这样一来,数据X肯定是抓不完整了,寄存器里原来有2个有效数据,现在只剩了1个有效数据(即数据Y),还有2个无效的数据(X的XL部分和Z的ZH部分)。如此,至少寄存器的利用率就大大折损了。

早期的68000处理器的“内存access粒度”乃是2 Bytes的,如上图所示。该处理器没有有效的处理非对齐地址的机制,而那时候的Mac OS对处理器因非对齐地址而抛出的异常也没有良好的处理机制。所以,当遭遇了上图中的非对齐地址的情况时,用户就不得不去重新启动电脑了。

最后,回到高级语言编程的环境中来,再扯扯。

当我们在C中构造一个如下的结构的时候:
  1. struct baz {
  2.     char c;  // 假设为1个Byte
  3.     int i;   // 假设为4个Bytes
  4.     short s; // 假设为2个Bytes
  5. };
复制代码
那么,baz类型的一个实例,将在内存中如下放置:
  1. Address       Memory         
  2. 0x0000          c               
  3. 0x0001         
  4. 0x0002         
  5. 0x0003         
  6. 0x0004          i
  7. 0x0005          i
  8. 0x0006          i
  9. 0x0007          i
  10. 0x0008          s
  11. 0x0009          s
  12. 0x000a
  13. 0x000b
  14. 0x000c         
  15. …… ……
复制代码
这样,如果CPU的“内存access粒度”合适的话,那么,CPU抓取每一个成员数据的时候,都可以一次性完成(原理同前所述)。

不过,我们也可以看出,存储器中的一些位置(以Bytes为单位)上没有存放什么“有用的数据”,比如在从0x0001到0x0003这3个Bytes上。所以,在某些“特别珍惜”数据所占空间的场合里面,这个实例的数据就有可能被“打包”,比如在某些网络传输过程中,该实例就有可能这样在数据空间序列中存在:
  1. Address       Memory         
  2. 0x0000          c               
  3. 0x0001          i
  4. 0x0002          i
  5. 0x0003          i
  6. 0x0004          i
  7. 0x0005          s
  8. 0x0006          s
  9. 0x0007          牛头
  10. 0x0008          牛头
  11. 0x0009          猪面
  12. …… ……
复制代码
这时候,数据存在的方式,不再是像之前那样的对齐的,而CPU去access数据的方式有可能还是按存储机构的地址对齐的。在这种情况下,如果我的意图是获取从0x0001开始的32 bits的数据,即实例的成员i的数据的话,那么,CPU有可能还是从0x0000抓取。如果CPU的“粒度”是4 Bytes,那么,它一次只能抓取到成员i的高3 Bytes的数据,(即从0x0001到0x0003上的,而0x0000上的数据没用),然后再来一次,抓取从0x0004到0x0007上的数据,其中只有0x0004上的数据有用,而从0x0005到0x0007上的成员s和半个牛头都没用。

所以,你可能就会想:有对齐,可真好呀——!

以上,仅供参考,呵呵:)

评分

参与人数 1可用积分 +4 收起 理由
lenky0401 + 4 虽然有点混乱,但回答得很认真。

查看全部评分

论坛徽章:
4
天秤座
日期:2013-10-18 13:58:33金牛座
日期:2013-11-28 16:17:01辰龙
日期:2014-01-14 09:54:32戌狗
日期:2014-01-24 09:23:27
10 [报告]
发表于 2012-08-28 13:09 |只看该作者
给楼上加2分吧,虽然还是没太看懂。
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP