分类 技术志 下的文章

常量类型的识别-一个小例子

今天闲时写了一个Demo测试程序,目的:测试64位编译下使用mmap映射共享内存的能力。程序很简单,大致如下结构:
#define MAP_SPACE_SIZE  (4*1024*1024*1024)
unsigned long int ms_sz = MAP_SPACE_SIZE;
…. ….
ptr = mmap( NULL, ms_sz, PROT_READ|PROT_WRITE,MAP_SHARED, fd, 0 );

我尝试在64位编译模式下映射4G的空间,结果mmap返回MAP_FAILED,errno返回EINVAL,通过查看mmap的manual得知,很可能是ms_sz这个参数的问题,当该参数实际值为0或<0时,mmap如是返回错误。输出一下ms_sz,居然真的是零,让我有些不解,但细致想了以后,觉得还是有道理的。

我遂尝试了重新定义MAP_SPACE_SIZE,结果印证了我的分析是正确的。
当#define MAP_SPACE_SIZE  (4*1024*1024*1024L)时,ms_sz输出 4294967296;
当#define MAP_SPACE_SIZE  4294967296时,ms_sz同样输出 4294967296;

这里简单说一下,首先 (4*1024*1024*1024)是不是常量呢?从程序的输出结果来看,编译器没有直接将其与数值常量4294967296等价,而是执行了计算过程。这也是我们第一次得到0这个结果的原因了。由于没有显式的后缀,编译器按照int, long, long long的顺序识别数值类型,编译器在识别4*1024*1024*1024中的各个数值时,显然将各个值识别为int了,而乘积的结果也放到了一个int临时存储区中,4G对于一个32bit的int刚好过庞大,结果溢出,导致该值变成了0,将0赋给ms_sz(unsigned long int),同样也是0,这就是原因。

当#define MAP_SPACE_SIZE  (4*1024*1024*1024L)时,由于显式给出了L后缀,编译器将运算结果直接存储在8 byte的long中,这样ms_sz自然很easy的得到了正确的值 4294967296。

当#define MAP_SPACE_SIZE  4294967296时,这时4294967296可是一个常量,标准的整型常量,编译器发现unsigned int无法将其装下,遂将之识别为long int类型了,这样该值赋给ms_size时就是同类型的了。

四谈内存对齐

最近在思考改进项目中一模块的实现,该模块维护起来让我很是头疼,所有才有了整体换掉它的想法。设计和实现中利用了内存对齐的技术。关于内存对齐,我曾经写过三篇文章,第一篇介绍了计算内存对齐的方法和例子,第二篇说了一个内存对齐的应用;三谈内存对齐时,则从其本质上做了阐述,而这次实际上是继续在其本质上的做文章,结合本质谈谈为什么内存对齐的计算方法就应该是第一篇中所讲的那两条。

如果对内存对齐本质还不清楚的话,就看看我的内存对齐系列的第三篇吧。如果你清楚了本质,那么我们结合第一篇中交待的内存对齐计算方法来进一步探究,为什么计算的方法就是这个样子的。

再理解一下对齐系数/模数,众所周知Alignment module反映了CPU 取数据时对数据起始地址的限制-这个地址值必须能被Alignment module所整除,但继续仔细考虑下去,你会想到CPU在下一次取数据依然要从下一个能被Alignment module所整除的地址的地方去取,这显而易见,又能说明什么呢?如果说CPU第一次取数据的地址是first_read_address,那么连续下一次的地址就应该是first_read_address + Alignment module了,也就是说每次取数据的量就是Alignment module这么多,这样通过Alignment module我们又可以知道一个量,那就是:在Alignement module限制下,CPU一次能取Alignment module个字节;在“Data alignment: Straighten up and fly right”一文中作者也称之为"memory access granularity"。从应用层开发人员的角度理解:被访问变量的长度,就是CPU要去读取的字节数;而对齐系数又是限制CPU读取字节数的一个指标,有了这两个理解,解决下面的疑问就有了基础了。

在'也谈内存对齐'一篇中介绍了内存对齐的计算方法,这里不妨再引用一下:
1、数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的对齐按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行。
2、结构(或联合)的整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。

疑问:对于数据成员对齐规则:为什么每个成员的对齐都要按照Min(指定对齐模数,数据成员自身长度)来确定呢?为了不用Max(指定对齐模数,数据成员自身长度)呢,用Max值对齐的不更加完美么?同样对于结构的整体对齐规则也一样有此疑问。这里我们还是举个例子更加直观:
#pragma pack(8)
struct Foo {
    char    a;
    int    b;
    short    c;
};
#pragma pack()
我们先来看数据成员对齐,以b为例子,按照规则的说法,sizeof(b) = 4 < 8,那么Address_of_b = Start_Address_of_Foo + 4; 我们来看看当应用的代码里访问b的时候,CPU做了些什么?Address_of_b一半情况下是不能被8整除的,在不能被8整除的情况下,我们去访问b,这里我要提两个问题:
1) 访问b的时候是否会因内存没有对齐到8上而触发core呢?(在Sun SPARC上因访问未对齐地址上的变量时会出core)
2) 为什么不将b放到Start_Address_of_Foo + 8这个地址上呢?

下面一一说说我的理解:
根据前面所说,程序在访问b的时候,CPU实际不一定是从Address_of_b这个地址上开始读取的。如果b这个地址恰巧既能被4整除,也能被8整除(如地址24),那就无可厚非了。但是如果这个地址只能被4整除,而不能被8整除(如地址12),那么此时CPU读取的地址肯定是从Address_of_b – 4开始读取8个字节的,也就是说实际上CPU都是从能被8整除的地址上读取的,而且一次读了8个字节,b所在的位置恰是这个8个字节中的后4个字节,所以不存在触发core的可能。

第二个问题,sizeof(b) = 4 < 8,为什么就要按照4而不是按照8去安排b的地址呢?我们不妨按照8去给b分配地址,Address_of_b' = Start_Address_of_Foo + 8,这样的话CPU也能一次将b读取,而且是从b的起始地址开始读,似乎更完美。但你看出问题了么?这么做浪费的空间显然大了很多。将b安排在Address_of_b'比安排在Address_of_b多浪费了一半空间。

同样整体对齐原则也是同样的道理。内存对齐计算显然有两个目标:一是减少CPU的访存次数;第二个就是还要保持存储空间的效率足够高。

如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! Go语言第一课 Go语言精进之路1 Go语言精进之路2 Go语言编程指南
商务合作请联系bigwhite.cn AT aliyun.com

欢迎使用邮件订阅我的博客

输入邮箱订阅本站,只要有新文章发布,就会第一时间发送邮件通知你哦!

这里是 Tony Bai的个人Blog,欢迎访问、订阅和留言! 订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠 ,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您希望通过微信捐赠,请用微信客户端扫描下方赞赏码:

如果您希望通过比特币或以太币捐赠,可以扫描下方二维码:

比特币:

以太币:

如果您喜欢通过微信浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:
本站Powered by Digital Ocean VPS。
选择Digital Ocean VPS主机,即可获得10美元现金充值,可 免费使用两个月哟! 著名主机提供商Linode 10$优惠码:linode10,在 这里注册即可免费获 得。阿里云推荐码: 1WFZ0V立享9折!


View Tony Bai's profile on LinkedIn
DigitalOcean Referral Badge

文章

评论

  • 正在加载...

分类

标签

归档



View My Stats