C语言再学习之内存对齐
昨天看Q3的代码,看到有个_INTSAIZEOF的宏,着实晕了一阵。一番google后,终于明白,这个宏的作用是求出变量占用内存空间的大小,先看看_INTSAIZEOF的定义吧:
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
(ANSI C标准下,_INTSAIZEOF宏定义在stdarg.h中,Q3中定义在bg_lib.h中;bg_lib.h -- standard C library replacement routines used by code)
关于这个宏的内部原理,我们后面再谈,还是先理理“内存对齐”这词的意思吧,多年来一直模糊的存在于我的大脑中,究竟为什么要内存对齐啊。
内存对齐的根源:
1、许多计算机系统对基本数据类型可允许地址作了一定的限制,要求某种类型对象的地址必须是某个值n(通常是2、4、8)的倍数,从而来简化处理器和存储器之间的接口的硬件设计。如Linux的对齐策略是2字节数据类型,例如short的地址必须是2的倍数。而较大的数据类型如:int、int*、float、double则必须是4的倍数。而Microsoft Windows的策略要求更为严格-----任何k字节对象的地址必须是k的倍数。比如要求一个double类型对象的地址必须是8的倍数(引自《深入理解计算机系统》)。
2、数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
内存对齐的规则:
上面所提到的自然边界是由什么决定的呢,我想应该是由硬件平台决定的,至于操作系统和编译器(默认对齐系数)则都是依赖于上一层的。当然,编译器的对齐系数可以通过预编译命令#pragma pack(n),n=1,2,4,8,16来改变;
举个例子:比如参数入栈,编译器并不是一个紧挨着一个的压入栈的,而是根据对齐系数来压栈的,比如一个char类型的参数,虽然本身只占一个字节,但是编译器会自动补全后面3个字节,然后再压下一个参数。
(这里说点题外话:在写过delphi程序的人都知道,有个packed的保留字,作用就是压缩数据结构,不要按对齐存储,除非数据结构体积庞大,否则为什么要用packed呢,用了不就影响内存读取的速度了吗?:))
1、数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的对齐按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行。
2、结构(或联合)的整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。
3、结合1、2颗推断:当#pragma pack的n值等于或超过所有数据成员长度的时候,这个n值的大小将不产生任何效果。
(以上3点引自《也谈内存对齐》一文)
再看_INTSAIZEOF宏:
这个宏的定义咋一看有点丈二和尚摸不着头脑,不过网上有对齐的解释,看后相信会豁然明朗了:
对于两个正整数 x, n 总存在整数 q, r 使得
x = nq + r, 其中 0<= r <n //最小非负剩余
q, r 是唯一确定的。q = [x/n], r = x - n[x/n]. 这个是带余除法的一个简单形式。在 c 语言中, q, r 容易计算出来: q = x/n, r = x % n.
所谓把 x 按 n 对齐指的是:若 r=0, 取 qn, 若 r>0, 取 (q+1)n. 这也相当于把 x 表示为:
x = nq + r', 其中 -n < r' <=0 //最大非正剩余
nq 是我们所求。关键是如何用 c 语言计算它。由于我们能处理标准的带余除法,所以可以把这个式子转换成一个标准的带余除法,然后加以处理:
x+n = qn + (n+r'),其中 0<n+r'<=n //最大正剩余
x+n-1 = qn + (n+r'-1), 其中 0<= n+r'-1 <n //最小非负剩余
所以 qn = [(x+n-1)/n]n. 用 c 语言计算就是:
((x+n-1)/n)*n
若 n 是 2 的方幂, 比如 2^m,则除为右移 m 位,乘为左移 m 位。所以把 x+n-1 的最低 m 个二进制位清 0就可以了。得到:
(x+n-1) & (~(n-1))
C语言再学习之内存对齐的更多相关文章
- C语言再学习part1-宏观认识C语言
天下莫柔弱于水,而攻坚强者莫之能胜,以其无以易之也.弱之胜强,柔之胜刚,天下莫不知行,莫能行. —老子 我近来每天都在坚持读书,所以我一直沉浸于古人的智慧中无法自拔.所以如果我这篇博文被你有幸看到,那 ...
- C语言深入学习系列 - 字节对齐&内存管理
用C语言写程序时需要知道是大端模式还是小端模式. 所谓的大端模式,是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中:所谓的小端模式,是指数据的低位保存在内存的低地址中,而数据的高 ...
- C语言 结构体的内存对齐问题与位域
http://blog.csdn.net/xing_hao/article/details/6678048 一.内存对齐 许多计算机系统对基本类型数据在内存中存放的位置有限制,它们会要求这些数据的首地 ...
- C语言结构体的内存对齐问题
在C语言开发当中会遇到这样的情况: #include <stdio.h> struct test { int a; char b; }; int main(int argc, const ...
- 3.c语言结构体成员内存对齐详解
一.关键一点 最关键的一点:结构体在内存中是一个矩形,而不是一个不规则形状 二.编程实战 #include <stdlib.h> #include <stdio.h> stru ...
- c语言基础学习08_内存管理
=============================================================================涉及到的知识点有:一.内存管理.作用域.自动变 ...
- C语言再学习part3—算法
君子远庖厨,万物皆备于我.—孟子 这篇文章主要总结程序的主要要素,以及程序的构成是什么样子的.最后说说我学到的一种奇特的表示算法的方式—伪代码. 让我们开始吧! 一个程序应该包括以下两个主要要素: 1 ...
- 01_c语言再学习_基础部分(1)
目录: 1.编译基础 2.c语言关键字 3.c语言数据类型 4.二进制/8进制/16进制 5.计算机内存数值存储方式:sizeof/原码/反码/补码 6.c语言中的字符和字符串 7.c语言中的数组和字 ...
- C语言再学习之 setjmp与longjmp
前不久在阅读Quake3源代码的时候,看到一个陌生的函数:setjmp,一番google和查询后,觉得有必要针对setjmp和longjmp这对函数写一篇blog,总结一下. setjmp和longj ...
随机推荐
- Git 教程
Git 教程 新建 模板 小书匠 欢迎使用 小书匠(xiaoshujiang)编辑器,您可以通过设置里的修改模板来改变新建文章的内容. Git使用 Git - 关于版本控制 TortoiseGit日常 ...
- STL的迭代器和类型萃取
今天就可以把STL库中迭代器的实现,和类型萃取好好整理一下了 迭代器的设计思维是STL的关键所在,在STL的实际运用和泛型思维,迭代器都扮演着十分重要的角色,STL力求把数据容器和算法的概念分开来,于 ...
- 使用自定义的framework
1.创建framework工程,创建需要的类将接口暴露在public中
- heap c++ 操作 大顶堆、小顶堆
在C++中,虽然堆不像 vector, set 之类的有已经实现的数据结构,但是在 algorithm.h 中实现了一些相关的模板函数.下面是一些示例应用 http://www.cplusplus.c ...
- Mvc 简单分页代码
) { string userid = EndUserLoginManage.Instance.loginUserID; ICommentInfoBLL c_bll = new CommentInfo ...
- 关于Scrum团队的理解
<阅读完<构建之法>第6~7章>之读后感 阅读完<构建之法>第6~7章之后,不仅感觉获益匪浅,也甚感团队合作.分配.工作的不易与一个团队运营一个项目并推广的艰辛与 ...
- css定位position认识
1.绝对定位(position: absolute) 2.相对定位(position: relative) 3.固定定位(position: fixed) 绝对定位 设置position:absolu ...
- redis连接超时报错
应用程序连接redis超时,报错如下: ERROR DubboServerHandler-xxx.xx.xx.52:20880-thread-172 2016-12-21 15:25:20,429 c ...
- emmet插件快捷键:
概念:emmet插件是用在编辑器里面的一个可以快速编写代码的插件,比如sublime text中,就可以用它来快速创建代码,本文主要是在sublime text的编辑器中做的测试代码. 一.html ...
- ASP.NET使用ConfigurationSection在Web.Config创建自定义配置节
主要代码,一定要继续System.Configuration.ConfigurationSection,具体的节点名称可以自行修改 using System; using System.Data; u ...