C语言结构体大小计算及对齐规则,附具体例子分析
难道你觉得结构体的大小是成员变量大小如此这般简单相加吗?那样或许会致使你的程序内存布局全然超乎想象,甚至引发性能方面的隐患。C语言里的结构体内存对齐规则,这个知识是每一个程序员都必定要掌握的底层内容。
为什么结构体大小不是简单相加
当今时代的CPU在对内存展开访问操作之际,并非是以一个字节为单位逐个进行读取的,而是依据字长作为单位来开展相关操作的。要是将一个占据4字节空间的int变量放置于地址为4的倍数的位置上,那么CPU能够一次性就把该变量取出;可要是其跨越了两个存储单元,那就需要进行两次内存访问操作,之后还得拼接数据,如此一来效率便会大幅降低。
编译器为使程序运行效率有所提升,会于结构体之中插入填充字节,以此保证每个成员均能对齐至其自然边界。这般做法乃是空间去更换时间的典型之举。以32位系统当做例子来讲,char类型会对齐于1字节边界,int则会对齐到4字节所在边界,double或者long long一般而言会对齐至8字节边界。
这种对齐规则看上去宛如浪费内存一般,然而对于那些被频繁访问的结构体数据来讲,性能提升是极为显著的。在诸如嵌入式开发或者网络协议解析等场景当中,理解对齐规则是格外重要的。
对齐单位与基本规则详解
每一种数据类型,都存在其默认的对齐单位,一般而言,是该类型本身的大小。其中,char是1字节,short是2字节,int在32位系统里是4字节,指针类型在32位系统中是4字节,在64位系统下是8字节。而这个所谓的对齐单位,决定了成员变量于结构体里的起始存放位置。
在处理结构体之际,编译器会遵循两条核心规则,其一为,对于每个成员,其起始偏移量得是其对齐单位的整数倍数,要是当下的偏移量不符合此条件,编译器便会于前面填充若干字节;其二是,整个结构体的总大小应当是结构体内最大对齐单位的整数倍数,倘若不足,便会在末尾填充字节。
例如存在这样一个结构体,其中包含int以及char,char所占位置是第0字节,int的存放起始位置为第4字节,在两者之间填充了3个字节,该结构体的整体大小是8字节,此大小刚好是int对齐单位的整数倍。
嵌套结构体的特殊处理方式
当结构体之中嵌套另外一个结构体之时,对齐规则会变得更为复杂不堪。外层结构体不会将内层结构体单纯地视作一个普通成员,而是要考量到内层结构体内部的最大对齐需求。内层结构体的整体对齐单位等同于其内部所有成员里最大的对齐单位。
编译器于处置内层结构体之际,务必要保证其起始偏移量成为此最大对齐单位的整数倍数。与此同时,内层结构体自身的大小早已经依据对齐规则予以调整了,外层结构体于计算总大小时,同样需将内层结构体所带来的对齐要求纳入考量。
这种嵌套的规则致使结构体的布局变得更为复杂,不过也确保了内层结构体内部的数据在任何情形之下都能够维持对齐。在实际开展编程的时候,对成员顺序作出调整能够明显改变结构体的总大小。
手把手计算复杂结构体大小
咱来剖析一个特定的实例:typedef struct { char a; int b; double c; char d; short e; long f; } STU。于64位系统的情形下,char a所占的字节数为0,int b需进行4字节对齐,所以从4开始占用4至7的位置,其间填充3字节。
两倍的c,其要求是8字节对齐,当前所处的偏移量,恰好就是8 ,完全恰好满足了,它所占用的范围是从8到15 ,char类型的 d 占用16 ,short类型的 e 是需要2字节对齐的,当前的位置是17 ,这时候不能满足,于是填充1字节后,从18开始占用到18至19 ,long类型的 f 需要8字节对齐,当前位置20 ,这是不满足的 ,经过填充4字节后,从24开始占用到24至31 ,最终这个结构体的大小是32字节 ,它是最大对齐单位8的整数倍 。
咱们再来瞧瞧嵌套结构体:typedef struct { char m; int x; STU s; double n; } SSTU。其中,char m所占空间为0 ,int x要进行4字节对齐,从4开始占用4到7这几个字节,STU s需8字节对齐,从8开始占用8到39这些字节,double n同样要8字节对齐,从40开始占用40到47这几个字节,总体大小为48字节。
编译器选项与平台差异
彼此不一样的编译器以及平台,有可能会采用相互不同的 alignment 规则。在 Windows 的情境里头,VC++ 的默认对齐量是 8 个字节,可是在 Linux 的状况之下,GCC 大概会依据系统具多少个位来产生变化。我们能够运用 #pragma pack(n) 这个指令去修改默认的对齐模式,把 n 设置成 1、2、4、8 这类数值。
设置#pragma pack(1)之际,编译器会取消对齐优化,所有成员紧密排列,结构体大小即为成员大小之合。于处理网络协议包或者文件格式之时,这颇为管用,然而会致使性能有所损失。还能够运用__attribute__((aligned(n)))去指定变量的对齐方式。
于实际开发里,要是结构体要实施跨平台运用或者写入文件,那么建议手动操控对齐方式,或者借助位域去确切操控内存布局。知晓这些编译器特性能够帮我们撰写出更为健壮的代码。
优化结构体布局的实用技巧
可经由对成员顺序予以合理安排,来使结构体大小得以减小。把相同类型的成员放置于一处,将大对齐单位的成员置于前面,把小对齐单位的成员置于后面,能够减少填充字节。比如说前面的STU结构体,要是先放置所有大类型,而后放置小类型,兴许可减少空间浪费。
于嵌入式系统或者内存受限的环境当中,这般优化极具价值。与此同时,还得留意结构体大小对缓存利用率所产生的影响,常常一同被访问的成员理应放置于同一个缓存行之内,以此规避伪共享问题。
对大型项目而言,能够运用offsetof宏去查验成员的偏移量,借助sizeof来核查总大小,以此助力理解内存布局。部分编译器还给出警告选项,当结构体存有不必要的填充情况时,能够对开发者提出优化提醒。
读完这篇文章,你对于结构体内存对齐所作之领会是否有所加深了呢,去查看你近日所撰之代码,究竟有无因忽视对齐之规则进而造成内存之浪费呀,可参与评论区说出于你遭遇到所涉对齐之bug,为这一篇文章点赞收藏,以使更多从事程序编程人员避开这些潜藏 pitfalls 焉。


