如何理解sizeof struct 对齐的内存对齐

C语言(3)
本文是对先前转载的一篇的学习和理解,主要以一个例子来分析,首先还是重新提一下理解这个例子的必备知识:
对于char型数据,其自身对齐值为1,对于short型为2,对于int,float,其自身对齐值为4,double类型,其自身对齐值为8,单位字节。
这里面有四个概念值:
1)数据类型自身的对齐值:就是上面交代的基本数据类型的自身对齐值。
2)指定对齐值:#pragma pack (value)时的指定对齐值value。
3)结构体或者类的自身对齐值:其成员中自身对齐值最大的那个值。
4)数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中较小的那个值。
&&& && 有了这些值,我们就可以很方便的来讨论具体数据结构的成员和其自身的对齐方式。有效对齐值N是最终用来决定数据存放地址方式的值,最重要。有效对齐N,就是表示“对齐在N上”,也就是说该数据的&存放起始地址%N=0&.而数据结构中的数据变量都是按定义的先后顺序来排放的。第一个数据变量的起始地址就是
数据结构的起始地址。结构体的成员变量要对齐排放,结构体本身也要根据自身的有效对齐值圆整(就是结构体成员变量占用总长度需要是对结构体有效对齐值的整 数倍,结合下面例子理解)。
下面以我自己写的一个例子程序来具体分析(基于Win 7 32位系统):
#include &stdio.h&
void main()
printf(&%d & %d&,sizeof(A),sizeof(B));
运行结果为:24 &32
sizeof(A)的结果分析:根据上面所说的,可以简单假设struct A{ }的第一个成员 char a的存储首地址为0x0000,对于char型数据,其自身对齐值为1,并且char型数据分配1个字节的内存;接着是short,其自身对齐值为2,也就是说该数据的&存放起始地址%2=0,所以该数据存储在0x(short型数据分配内存2个字节);然后是double
c,double型自身对齐值为8,所以该数据存储在0xf(该数据的&存放起始地址%8=0);接着是int d,int型自身对齐值为4,所以可以连续存储在0x3(该数据的&存放起始地址%8=0);最后结构体自身为了保持圆整,会以成员中&有效对齐N&最大的8进行对齐,可以将这段内存看成是连续的(在这里会延伸到0x0017),中间空的地方用0填充,及考虑内存地址对齐之后struct
A的占用的内存单元是0x00~0x17共24个字节;
接着分析struct B,同样假设,将char e存放在0x00,由对sizeof(A)的结果分析可以知道,struct A 以8字节对齐,(还是假设以一段连续的内存单元来存储)所以,A a的存储首地址为0x08~0x1f(注意A a 实际占用24个字节),然后用0填充,所以共32个字节。
注意:如果结构体中有数组,只需将数组看成若干个相同的单个同类型的数据,例如,将char a 改成char a [5],怎可当成是连续定义了5个char型数据。
说完了struct ,接下来再说一下 union,首先摘几句The C Programming Language里面讲述这个问题的原话:
①联合就是一个结构
②它的所有成员相对于基地址的偏移量都为0
③此结构空间要大到足够容纳最“宽”的成员
④并且,其对齐方式要适合于联合中所有类型的成员。
附图说明:
该结构要放得下int i[5]必须要至少占4×5=20个字节。如果没有double的话20个字节够用了,此时按4字节对齐。但是加入了double就必须考虑double的对齐方式,double是按照8字节对齐的,所以必须添加4个字节使其满足8×3=24,也就是必须也是8的倍数,这样一来就出来了24这个数字。综上所述,最终联合体的最小的size也要是所包含的所有类型的基本长度的最小公倍数才行。(这里的字节数均指win
32位下的值,平台、编译器不同值也有可能不同。)
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:62672次
积分:1227
积分:1227
排名:千里之外
原创:57篇
转载:33篇
(6)(1)(1)(1)(2)(1)(1)(2)(1)(1)(5)(1)(4)(4)(1)(2)(5)(5)(1)(10)(9)(1)(6)(12)(7)温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!&&|&&
相信自己,把握现在!!!
LOFTER精选
网易考拉推荐
用微信&&“扫一扫”
将文章分享到朋友圈。
用易信&&“扫一扫”
将文章分享到朋友圈。
阅读(1203)|
用微信&&“扫一扫”
将文章分享到朋友圈。
用易信&&“扫一扫”
将文章分享到朋友圈。
历史上的今天
loftPermalink:'',
id:'fks_080074',
blogTitle:'C#中struct的内存对齐',
blogAbstract:'\r\n\r\n\r\n\r\n很少有人谈起struct的内存对齐问题, 就是在很多C#书中, 也很少提及. 但在实际应用中, 如果不注意内存对齐, struct比较大的话, 则会浪费一定的内存.&&& 先从一个实例看起.public unsafe struct MyStruct1{&&&&&&&&}&&& 在这个struct中, 各个成员的字节数为, b:1, s:4, i:4, c:1. s为指针,',
blogTag:'',
blogUrl:'blog/static/',
isPublished:1,
istop:false,
modifyTime:0,
publishTime:7,
permalink:'blog/static/',
commentCount:0,
mainCommentCount:0,
recommendCount:0,
bsrk:-100,
publisherId:0,
recomBlogHome:false,
currentRecomBlog:false,
attachmentsFileIds:[],
groupInfo:{},
friendstatus:'none',
followstatus:'unFollow',
pubSucc:'',
visitorProvince:'',
visitorCity:'',
visitorNewUser:false,
postAddInfo:{},
mset:'000',
remindgoodnightblog:false,
isBlackVisitor:false,
isShowYodaoAd:false,
hostIntro:'相信自己,把握现在!!!',
hmcon:'0',
selfRecomBlogCount:'0',
lofter_single:''
{list a as x}
{if x.moveFrom=='wap'}
{elseif x.moveFrom=='iphone'}
{elseif x.moveFrom=='android'}
{elseif x.moveFrom=='mobile'}
${a.selfIntro|escape}{if great260}${suplement}{/if}
{list a as x}
推荐过这篇日志的人:
{list a as x}
{if !!b&&b.length>0}
他们还推荐了:
{list b as y}
转载记录:
{list d as x}
{list a as x}
{list a as x}
{list a as x}
{list a as x}
{if x_index>4}{break}{/if}
${fn2(x.publishTime,'yyyy-MM-dd HH:mm:ss')}
{list a as x}
{if !!(blogDetail.preBlogPermalink)}
{if !!(blogDetail.nextBlogPermalink)}
{list a as x}
{if defined('newslist')&&newslist.length>0}
{list newslist as x}
{if x_index>7}{break}{/if}
{list a as x}
{var first_option =}
{list x.voteDetailList as voteToOption}
{if voteToOption==1}
{if first_option==false},{/if}&&“${b[voteToOption_index]}”&&
{if (x.role!="-1") },“我是${c[x.role]}”&&{/if}
&&&&&&&&${fn1(x.voteTime)}
{if x.userName==''}{/if}
网易公司版权所有&&
{list x.l as y}
{if defined('wl')}
{list wl as x}{/list}一.内存对齐的初步讲解
内存对齐可以用一句话来概括:
&数据项只能存储在地址是数据项大小的整数倍的内存位置上&
例如int类型占用4个字节,地址只能在0,4,8等位置上。
#include &stdio.h&struct xx{&&&&&&&&&&&&&&&&&&&&&&&&&&&&};
int main(){&&&&&&&&&&&&&& printf("&a = %p/n", &bb.a);&&&&&&& printf("&b = %p/n", &bb.b);&&&&&&& printf("&c = %p/n", &bb.c);&&&&&&& printf("&d = %p/n", &bb.d);&&&&&&& printf("sizeof(xx) = %d/n", sizeof(struct xx));
&&&&&&& return 0;}
执行结果如下:
&a = ffbff5ec&b = ffbff5e8&c = ffbff5f0&d = ffbff5f4sizeof(xx) = 16
会发现b与a之间空出了3个字节,也就是说在b之后的0xffbff5e9,0xffbff5ea,0xffbff5eb空了出来,a直接存储在了0xffbff5ec, 因为a的大小是4,只能存储在4个整数倍的位置上。打印xx的大小会发现,是16,有些人可能要问,b之后空出了3个字节,那也应该是13啊?其余的3个 呢?这个往后阅读本文会理解的更深入一点,这里简单说一下就是d后边的3个字节,也会浪费掉,也就是说,这3个字节也被这个结构体占用了.
可以简单的修改结构体的结构,来降低内存的使用,例如可以将结构体定义为:struct xx{&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&};
这样打印这个结构体的大小就是12,省了很多空间,可以看出,在定义结构体的时候,一定要考虑要内存对齐的影响,这样能使我们的程序占用更小的内存。
二.操作系统的默认对齐系数
每 个操作系统都有自己的默认内存对齐系数,如果是新版本的操作系统,默认对齐系数一般都是8,因为操作系统定义的最大类型存储单元就是8个字节,例如 long long定要这样,不存在超过8个字节的类型(例如int是4,char是1,long在32位编译时是4,64位编译时是 8)。当操作系统的默认对齐系数与第一节所讲的内存对齐的理论产生冲突时,以操作系统的对齐系数为基准。(例如 操作系统默认的对齐系数为 4 , 那么char类型的数据就只能存在0 , 4 ,8 等4的倍数地址上;&假设操作系统的默认对齐系数是4,那么对与long long这个类型的变量就不满足第一节所说的,也就是说long long这种结构,可以存储在被4整除的位置上,也可以存储在被8整除的位置上。)
可以通过#pragma pack()语句修改操作系统的默认对齐系数,
#include &stdio.h&#pragma pack(4)struct xx{&&&&&&&&&&&&&&&&&&&&&&&&&&&&};#pragma pack()
int main(){&&&&&&&&&&&&&& printf("&a = %p/n", &bb.a);&&&&&&& printf("&b = %p/n", &bb.b);&&&&&&& printf("&c = %p/n", &bb.c);&&&&&&& printf("&d = %p/n", &bb.d);&&&&&&& printf("sizeof(xx) = %d/n", sizeof(struct xx));
&&&&&&& return 0;}打印结果为:
&a = ffbff5e4&b = ffbff5e0&c = ffbff5ec&d = ffbff5f0sizeof(xx) = 20
发现占用8个字节的a,存储在了不能被8整除的位置上,存储在了被4整除的位置上,采取了操作系统的默认对齐系数。
三.内存对齐产生的原因
内存对齐是操作系统为了快速访问内存而采取的一种策略,简单来说,就是为了放置变量的二次访问。操作系统在访问内存 时,每次读取一定的长度(这个长度就是操作系统的默认对齐系数,或者是默认对齐系数的整数倍)。如果没有内存对齐时,为了读取一个变量是,会产生总线的二 次访问。
例如假设没有内存对齐,结构体xx的变量位置会出现如下情况:
struct xx{&&&&&&&&&&&&&&& //0xffbff5e8&&&&&&&&&&&&&&&&&& //0xffbff5e9&&&&&&&&&&&&&&&&&&&&&&&&&& //0xffbff5ed&&&&&&&&&&&&&&&&&&&&& //0xffbff5f1};
操作系统先读取0xffbff5e8-0xffbff5ef的内存,然后在读取0xffbff5f0-0xffbff5f8的内存,为了获得值c,就需要将两组内存合并,进行整合,这样严重降低了内存的访问效率。(这就涉及到了老生常谈的问题,空间和效率哪个更重要?这里不做讨论)。
这样大家就能理解为什么结构体的第一个变量,不管类型如何,都是能被8整除的吧(因为访问内存是从8的整数倍开始的,为了增加读取的效率)!
内存对齐的问题主要存在于理解struct等复合结构在内存中的分布。
首先要明白内存对齐的概念。许多实际的计算机系统对基本类型数据在内存中存放的位置有限制,它们会要求这些数据的首地址的值是某个数k(通常它为4或8)的倍数,这就是所谓的内存对齐。
这个k在不同的cpu平台下,不同的编译器下表现也有所不同。比如32位字长的计算机与16位字长的计算机。我们的开发主要涉及两大平台,windows和linux(unix),涉及的编译器也主要是microsoft编译器(如cl),和gcc。
内存对齐的目的是使各个基本数据类型的首地址为对应k的倍数,这是理解内存对齐方 式的终极法宝。另外还要区分编译器的分别。明白了这两点基本上就能搞定所有内存对齐方面的问题。
不同编译器中的k:1、对于microsoft的编译器,每种基本类型的大小即为这个k。大体上char类型为8,int为32,long为32,double为64。2、对于linux下的gcc编译器,对于大于等于4字节的成员的起始&位置应该处在4字节的整数倍上,对于2字节成员的起始位置应该处在2字节的整数倍上,1字节的起始位置任意。
明白了以上的说明对struct等复合结构的内存分布就应该很清楚了。
下面看一下最简单的一个类型:struct中成员都为基本数据类型,例如:struct test1{};
在windows平台,microsoft编译器下:
假设从0地址开始,首先a的k值为1,它的首地址可以使任意位置,所以a占用第一个字节,即地址0;然后b的k值为2,他的首地址必须是2的倍数,不能是1,所以地址1那个字节被填充,b首地址为地址2,占用地址2,3;然后到c,c的k值为4,他的首地址为4的倍数,所以首地址为4,占用地址4,5,6,7;再然后到d,d的k值也为4,所以他的首地址为8,占用地址8,9,10,11。最后到e,他的k值为8,首地址为8的倍数,所以地址12,13,14,15被填充,他的首地址应为16,占用地址16-23。显然其大小为24。
在linux平台,gcc编译器下:假设从0地址开始,首先a的k值为1,它的首地址可以使任意位置,所以a占用第一个字节,即地址0;然后b的k值为2,他的首地址必须是2的倍数,不能是1,所以地址1那个字节被填充,b首地址为地址2,占用地址2,3;然后到c,c的k值为4,他的首地址为4的倍数,所以首地址为4,占用地址4,5,6,7;再然后到d,d的k值也为4,所以他的首地址为8,占用地址8,9,10,11。最后到e,从这里开始与microsoft的编译器开始有所差异,他的k值为不是8,仍然是4,所以其首地址是12,占用地址12-19。显然其大小为20。
接下来,看一看几类特殊的情况,为了避免麻烦,不再描述内存分布,只计算结构大小。
第一种:嵌套的结构
struct test2{struct test1};
在windows平台,microsoft编译器下:
这种情况下如果把test2的第二个成员拆开来,研究内存分布,那么可以知道,test2的成员f占用地址0,g.a占用地址1,以后的内存分布不变,仍然满足所有基本数据成员的首地址都为其对应k的倍数这一原则,那么test2的大小就还是24了。但是实际上test2的大小为32,这是因为:不能因为test2的结构而改变test1的内存分布情况,所以为了使test1种各个成员仍然满足对齐的要求,f成员后面需要填充一定数量的字节,(可能是windows默认的对齐系数为8 , 所以struct需要从8的倍数开始存储,因为对于结构体,它的大小应该是编译器最大对齐模数的整数倍))不难发现,这个数量应为7个,才能保证test1的对齐。所以test2相对于test1来说增加了8个字节,所以test2的大小为32。
在linux平台,gcc编译器下:
同样,这种情况下如果把test2的第二个成员拆开来,研究内存分布,那么可以知道,test2的成员f占用地址0,g.a占用地址1,以后的内存分布不变,仍然满足所有基本数据成员的首地址都为其对应k的倍数这一原则,那么test2的大小就还是20了。但是实际上test2的大小为24,同样这是因为:不能因为test2的结构而改变test1的内存分布情况,所以为了使test1种各个成员仍然满足对齐的要求,f成员后面需要填充一定数量的字节(估计是因为Linux的默认对齐系数为4 , 所以struct成员需要从4 的倍数开始存储,因为对于结构体,它的大小应该是编译器最大对齐模数的整数倍),不难发现,这个数量应为3个,才能保证test1的对齐。所以test2相对于test1来说增加了4个字节,所以test2的大小为24。
关于嵌套的struct的存储仍然不明白, 待以后更改。
原文地址 : & /kex1n/archive//2286527.html
阅读(...) 评论()

我要回帖

更多关于 struct 对齐 的文章

 

随机推荐