编程技术是改变世界的力量。
本站
当前位置:网站首页 > 后端语言 > 正文

三分钟读懂编程语言C内存地址对齐与struct大小判断

gowuye 2024-04-25 04:42 10 浏览 0 评论

在笔试时,经常会遇到结构体大小的问题,实际就是在考内存地址对齐。在实际开发中,如果一个结构体会在内存中高频地分配创建,那么掌握内存地址对齐规则,通过简单地自定义对齐方式,或者调整结构体成员的顺序,可以有效地减少内存使用。另外,一些不用边界对齐、可以在任何地址(包括奇数地址)引用任何数据类型的的机器,不在本文讨论范围之内。

什么是地址对齐

计算机读取或者写入存储器地址时,一般以字(因系统而异,32位系统为4个字节)大小(N)的块来执行操作。数据对齐就是将数据存储区的首地址对齐字大小(N)的某个整数倍地址。为了对齐数据,有时需要在物理上相邻的两个数据之间保留或者插入一些无意义的字节。内存对齐本事编译器考虑是事情,但在C、C++语言中,可以人为修改对齐方式。如果你想加入我们 跟我们一起学习的话可以加C/C++从入门到大牛 Ⅱ369203660 我们一起共同进步和学习!

为什么要地址对齐

计算机会保证存储器字的大小,至少要大于等于计算机支持的最大原始数据类型的大小。

这样,一个原始数据类型就一定可以存放在一个存储器字中,如果保证了数据是地址对齐的,那么访问一个原始数据就可以保证只访问一个存储器字,这有利于提高效率。如下图

反之,如果一个数据不是按字大小内存对齐的(也就是最高字节与最低字节落在两个字中),那么,这个数据很可能落在两个存储器字中。如下图

这时,计算机必须将数据访问分割成多个存储器字访问,这需要更多复杂的操作。甚至,当这两个字都不存在一个存储器页中是,处理器还必须在执行指令之前验证两个页面是否存在,否则可能会发生未命中错误。另外,对一个存储器字的操作是原子的,如果拆分成两次访问,也可能引发一些并发问题,比如从两个字读出来的数据段拼起来可能不是真实的数据,因为有另外的设备在写。

起始地址约束(对齐系数)

C++11 引入 alignof 运算符,该运算符返回指定类型的对齐系数(以字节为单位),其中宏__alignof在linux gcc或者windows都有定义。

下面一段程序取几个常用的基本数据类型。

C/C++从入门到大牛 Ⅱ369203660

1 #include <stdio.h> 2 #include <stdlib.h> 3 int main(){ 4 printf("char: %d\n",__alignof(char)); 5 printf("short: %d\n",__alignof(short)); 6 printf("int: %d\n",__alignof(int)); 7 printf("long: %d\n",__alignof(long)); 8 printf("double: %d\n",__alignof(double)); 9 return 0;10 }

分别在linux和windows下编译运行,得到如下结果

类型LinuxWindows
char11
short22
int44
long84
double88

可以看到Linux下与Windows下,long类型对齐系数不一样。并且对齐系数与类型自身所占的大小也基本一致。

地址对齐对struct大小的影响

地址对齐主要影响到一些复杂的数据结构,比如struct结构体,因为有了内存地址对齐,大多数的struct实际占用的大小显得有些诡异。(注意,一个结构体的大小很可能超过存储器字大小,这时跨字读取数据已不可避免。但结构体本身及其成员还是需要继续遵守对齐规则)

拿一个很简单的结构体align1为例

C/C++从入门到大牛 Ⅱ369203660

1 struct align12 {3 char a;4 int b;5 char c;6 } sim[2];

如果不考虑任何对齐问题,只考虑结构体中每个成员应该占用的大小,很显然每个结构align1定义的变量是1(char)+4(int)+1(char)共6个字节。但是实际上(至少在windows上)它占用了12个字节,原因就在于它有按照一定的规则进行内存地址对齐。下面是笔者参考各方面资料总结的四点结构体边界对齐需满足的要点:如果你想加入我们 跟我们一起学习的话可以加C/C++从入门到大牛 Ⅱ369203660 我们一起共同进步和学习!

  1. 结构体变量本身的起始位置,必须是结构成员中对边界要求最严格(对齐系数最大)的数据类型所要求的位置

  1. 比如double类型的起始地址约束(对齐系数)为8 ,那如果一个结构体包含double类型,则结构体变量本身的起始地址要能被8整除

  1. 成员必须考虑起始地址约束(对齐系数)和本身的大小,在windows和linux下,都可以使用__alignof(type)来查看type类型(原始基本类型)的起始地址约束(对齐系数)。

  2. 如果成员也是struct union之类的类型,则整体要照顾到部分,整体要满足成员能符合起始地址约束

  3. 结构体可能需要在其所有成员之后填充一些字节,以保证在分配结构体数组之后,每个数组元素要满足起始地址约束。

让我们再来仔细研究下结构体 align1定义的实例数组 sim[2]。我们先约定:占用即表示本身大小及其后的空余空间。

按要点1,则sim[0]的起始地址必须能被4整除,假设这个其实地址是4n,其中成员a的起始地址也是sim[0]的起始地址(按要点2,因为a 为char类型,对齐系数为1,放哪都可以),a占用一个字节。

按要点2,成员b的起始地址必须能被4整除,很显然不能直接放在成员a的后面(起始地址是4n+1,不能被4整除),所以需要跳过3个字节存放b,那么成员a实际占用了4个字节(我们的约定)。

同理,成员c可以直接放在b成员后面(起始地址是(4(n+2)),而且肯定可以被1整除)。

至此,sim[0]已经占用了9个字节了,但按照要点4,因为数组是连续的,为了保证其后的数组成员sim[1]也符合首地址能被4整除,必须将sim[0]的空间先后延长3个字节至(4(n+3))。所以sim[0]实际要占用12个字节。

当然一个结构体不能有两个大小,哪怕其后不再放align1类型的变量,系统也要为这个变量分配最大的12个字节空间。

用一个简单的占位符来表示存储,可表示为

1 // --sim[0]---- ----sim[1]--2 // a---bbbbc--- a---bbbbc---

用图片描述如图(一个正方形表示一个字节空间)

很显然,这个结构体对空间利用率不高,有50%的空间浪费。通过调整成员定义的顺序,完全可以优化空间利用。个人的经验是,本身占用空间大的(如double类型)应该尽量往前面放。下面我们将int b;调整到第一位定义

C/C++从入门到大牛 Ⅱ369203660

1 struct align22 {3 int b;4 char a;5 char c;6 } sim[2];

通过分析不难发现,新的结构占用8个字节的空间。如图

C/C++从入门到大牛 Ⅱ369203660

空间利用率提高到75%。当一个结构体足够复杂时,通过调整顺序或者自定义对齐方式,压缩带来的空间是非常可观的。虽然,随着内存越做越大,一般情况下开发已经不需要考虑这种问题。但是在海量服务下,如何死抠性能和减少资源占用依然是开发需要考虑的问题。就像现在单机几十万并发tcp连接已经不难做到,为什么还是有很多人在研究C10M(单机千万连接)。

下面的程序是基于以上四项要点做的测试,特别注意MyStruct7,因为其中的成员包含数组。至于成员包含union的就比较简单了,一般可以直接把union用union中最大的成员替换考虑,另外注意考虑要点3。另外,在一个位段定义中使用非int 、signed int 、或者unsigned int类型,位段定义将变成一个普通的结构体,对齐原则也就遵从结构体的对齐原则。

测试代码

文中所用的windows为windows7 64位, gcc版本为:gcc version 5.1.0 (tdm64-1);linux为CentOSLinux release 7.2.1511 (Core),gcc版本是gcc version 4.8.5 20150623 (Red Hat 4.8.5-11) (GCC)


请养成良好的阅读习惯,看完如果觉得喜欢的话请关注转发评论收藏一下 感谢!

相关推荐

R语言数据挖掘实践——支持向量机的常用函数
R语言数据挖掘实践——支持向量机的常用函数

e1071包是R语言中用于支持向量机建模与分析的软件包,其主要用于支持向量机的模型构建,提供核心函数svm()来建立支持向量机的基础模型,并且可辅助使用pred...

2024-05-18 12:15 gowuye

R数据分析:如何做聚类分析,实操解析
R数据分析:如何做聚类分析,实操解析

Clusteringisabroadsetoftechniquesforfindingsubgroupsofobservationswi...

2024-05-18 12:14 gowuye

用R语言做数据分析——马赛克图
用R语言做数据分析——马赛克图

到目前为止,我们已经学习了许多可视化定量或连续型变量间关系的方法。但如果变量是类别型的呢?若只观察单个类别型变量,可以使用柱状图或者饼图;若存在两个类别型变量,...

2024-05-18 12:14 gowuye

用R语言做数据分析——方差分析基本概论
用R语言做数据分析——方差分析基本概论

在实际工作中,影响一件事的因素是很多的,我们总是希望通过各种试验来观察各种因素对试验结果的影响。例如,不同的生产厂家、不同的原材料、不同的操作规程,以及不同的技...

2024-05-18 12:14 gowuye

R语言数据分析实战:数据清洗与可视化
R语言数据分析实战:数据清洗与可视化

《R语言数据分析实战:数据清洗与可视化》是一本深入浅出的实践指南,专为对数据分析感兴趣的读者精心编撰。本书旨在帮助读者掌握R语言这一强大的统计分析工具,通过实例...

2024-05-18 12:13 gowuye

用R语言做数据分析——双因素方差分析
用R语言做数据分析——双因素方差分析

在双因素方差分析中,受试者被分配到两因子的交叉类别组中。以基础安装中的Tooth-Growth数据集为例,随机分配60只豚鼠,分别采用两种喂食方法(橙汁或维生素...

2024-05-18 12:13 gowuye

用R语言做数据分析——独立两样本和K样本检验
用R语言做数据分析——独立两样本和K样本检验

coin包简介对于独立性问题,coin包提供了一个进行置换检验的一般性框架,通过这个包,我们可以回答如下问题:响应值与组的分配独立吗?两个数值变量独立吗?两个类...

2024-05-18 12:13 gowuye

用R语言做数据分析——用回归做方差分析
用R语言做数据分析——用回归做方差分析

之前提到方差分析和回归都是广义线性模型的特例,之前文章的所有设计都可以用lm()函数来分析。为了更好地理解输出结果,需要弄明白在拟合模型时,R语言是如何处理类别...

2024-05-18 12:13 gowuye

数据分析R语言——数据结构
数据分析R语言——数据结构

数据分析R语言——数据结构数组数组(array)与矩阵类似,但是维度可以大于2.数组通过array()函数创建。形式如;myarray<-array(v...

2024-05-18 12:13 gowuye

R语言数据挖掘实践——关联分析的常用函数
R语言数据挖掘实践——关联分析的常用函数

arules和arulesViz是R语言中两个专用于关联分析的软件包。其中arules用于关联规则的数字化生成,提供Apriori和Eclat这两种快速挖掘频繁...

2024-05-18 12:12 gowuye

R语言数据挖掘实践——判别分析的常用函数
R语言数据挖掘实践——判别分析的常用函数

判别算法在R语言中实现主要涉及4个软件包中的相关函数,它们依次为MASS、klaR、class和kknn。其中MASS包含有大量实用而先进的统计计数函数及适用数...

2024-05-18 12:12 gowuye

用R语言读取Excel、PDF和JSON文件,终于有人讲明白了
用R语言读取Excel、PDF和JSON文件,终于有人讲明白了

导读:本文将讨论Excel、PDF等文件的读取,以及相应函数的参数设置。作者:刘健邬书豪如需转载请联系华章科技下图总结了主要程序包,希望读者在日常练习和工作中...

2024-05-18 12:12 gowuye

R语言数据挖掘实践——聚类分析的常用函数
R语言数据挖掘实践——聚类分析的常用函数

使用R语言可以轻松实现聚类分析,stats、cluster、fpc和mclust是常用的四个聚类分析软件包。stats主要包含一些基本的统计函数,如用于统计计算...

2024-05-18 12:12 gowuye

用R语言做数据分析——时间序列分类
用R语言做数据分析——时间序列分类

时间序列分类是根据已标注的时间序列建立一个分类模型,然后使用分类模型预测未标记时间序列的类别。从时间序列中抽取出新特征肯呢个有助于提高分类模型的性能。特征提取技...

2024-05-18 12:11 gowuye

一文看懂用R语言读取Excel、PDF和JSON文件(附代码)
一文看懂用R语言读取Excel、PDF和JSON文件(附代码)

导读:本文将讨论Excel、PDF等文件的读取,以及相应函数的参数设置。作者:刘健邬书豪如需转载请联系华章科技下图总结了主要程序包,希望读者在日常练习和工作中...

2024-05-18 12:11 gowuye

取消回复欢迎 发表评论: