当前位置: 首页 > news >正文

计算结构体及其中元素的大小

计算结构体及其中元素的大小

本文验证环境:Microsoft Visual Studio Professional 2019 (2) 版本 16.11.25

语言标准:C++(ISO C++14 标准) C(旧 MSVC)

项目类型:win32

在接触C++八股文的过程中,回顾了关于结构体内存对齐的知识,另外还有一些新的自定义对齐方式的方法,特此说明并举例一二。

内存对齐

内存对齐的原因

提高访存效率。尽管内存以字节为单位,但处理器大多并非以字节为单位来存取内存,它一般会以2、4、8、16甚至32字节为单位来存取内存,称为内存存取粒度。
假如没有对齐,数据任意存放,一个int变量存放在从地址1开始的连续四个字节中,CPU取数据时,先从0地址开始读取第一个4字节块,剔除不想要的字节(0地址),
然后从地址4开始读取下一个4字节块,同样剔除不要的数据(5,6,7地址),最后留下的两块数据合并放入寄存器,这需要做很多工作;
有了内存对齐,int只能存放在按照对齐规则的地址中,比如说0地址开始的内存,处理器在取数据时一次就能将数据读出,不需要额外操作,提高了效率。

基本规则(自动对齐)

在一般情况下,

  1. 结构体第一个成员的偏移量(offset)为0
  2. 此后每个成员的偏移量都是该成员大小的整数倍,如有需要编译器会在成员之间加上填充字节
  3. 结构体的总大小为最大成员大小的整数倍,如有需要编译器会在最末一个成员之后加上填充字节。

注意,成员变量在内存中存储的顺序,严格按照结构体定义顺序排布,所以在定义时要尽可能把成员变量按大小依次声明(从小到大或从大到小),不要大小穿插。
如下例x2,就浪费了很多空间。

但是,这好像也不绝对,按照11241124的方式定义结构体好像也没什么问题。

举例(基本规则比较简单,可以不看):

struct {int i;char c1;char c2;
} x1;
//printf("%d\n",sizeof(x1));  // 8struct {char c1;int i;char c2;
} x2;
//printf("%d\n",sizeof(x2));  // 12struct {char c1;char c2;int i;
} x3;
//printf("%d\n",sizeof(x3));  // 8

这一部分比较简单,不再赘述。主要看下面的部分。

指定对齐方式的规则

规范指明 结构体边界 的字节对齐方式

c++11以后引入两个关键字alignasalignof

alignof可以计算结构体的对齐方式。

alignas可以指定结构体的对齐方式(可指定1,2,4,8,16,…,8192(13次方)等2的次方个字节的对齐方式,指定0为默认对齐方式)。
这个对齐方式仅在一定条件下影响结构体大小,不影响内部成员变量。

此处使用alignas(1<<28)的写法可以测得该参数最大可用268435456(28次方),编译成功但代码标红,CLION平台vs,mingw编译方式实测。
遗憾的是ai助手只是说语言规范并没有限制这个对齐参数的大小,可能是编译器或平台限制了,编译器为什么都限制这个大小,却没有说。

然而,alignas可能会失效

然而alignas只能限定结构体边界的对齐粒度(仅结构体大小是这粒度的整数倍,成员存储仍然是自动对齐,其偏移量不一定是这个的整数倍),
而且在某成员变量比这个粒度大的情况下会失效(IDE也会提示,这个值不能小于默认对齐):

struct Info1 {char a;int b;char c;
};
//    std::cout << sizeof(Info1) << "\t/" << alignof(Info1) << std::endl;   // 12 = 4 + 4 + 4     / 4// alignas 生效的情况
struct alignas(8) Info2 {char a;int b; // 注意!根据空间占用结果来看,这个变量并 不 是 从 8 字 节 的 整 数 倍 地 址 开 始 存 储 的!char c;
};
//    std::cout << sizeof(Info2) << "\t/" << alignof(Info2) << std::endl;   // 16 = 8 + 8         / 8// alignas 失效的情况
struct alignas(2) Info3 { // IDE中这个alignas会标红,提示,对齐不能小于默认对齐char a;int b; // 到此处默认对齐大小变为4字节,大于alignas值,alignas失效,对齐粒度为4字节char c;
};
//    std::cout << sizeof(Info3) << "\t/" << alignof(Info3) << std::endl;   // 12 = 4 + 4 + 4     / 4
有的人连字节和比特位都分不清,也敢在网上写博客,真是让我大开眼界
平台各异 元素偏移量 的非标字节对齐
gnu c gcc

单字节对齐方法:__attribute__((packed)) (取消编译过程的优化对齐,按实际占用对齐,其实就是不对齐了,GCC特有的语法)

未验证差异点:gcc编译器不是紧凑模式的(不紧凑就会对齐产生空字节),vc编译器不是紧凑的,tiny c的编译器是紧凑的
__attribute__:可以设置函数属性(Function Attribute)、变量属性(Variable Attribute)和类型属性(Type Attribute),是gnu c的特色
packed属性:使用该属性可以使得变量或者结构体成员使用最小的对齐方式,即对变量是一字节对齐,对域(field)是位对齐。
位域,以位为单位的明确大小的类数据成员,相邻位域可以跨过(跨两个不完整的字节存储)、共享相邻字节
(结构体实测,其不能跨越两个定义类型,一个域再大也只能放在一个定义类型的空间中)。

多字节对齐:__attribute__((aligned(2))) 2 字节对齐(按照网上说的是这样的,虚拟机写c又是编译又是执行的,太麻烦了,回头写个脚本方便了再试,待验证)

visual studio、vc
单字节对齐方法

#pragma pack(push,1) 含义:把原来对齐方式设置压栈,并设新的对齐方式为一个字节。

#pragma pack(push) //对齐方式压栈保存
#pragma pack(1)//调整结构体的边界,设定为1字节对齐
// 这两句 相当于 #pragma  pack (push,1)
多字节对齐方法

#pragma pack(4)#pragma pack (push,4)

恢复对齐状态

#pragma pack(pop) 恢复上个对齐状态(对使用过#pragma pack(push)而言)

#pragma pack () 恢复默认对齐方式。

元素存储位数控制

// 本人在vs中测试,对于注释部分的说明,如果在 gnu c 中可能会有不一样的结果#if defined(__GNUC__) || defined(__GNUG__)#define ONEBYTE_ALIGN __attribute__((packed))
#elif defined(_MSC_VER)#define ONEBYTE_ALIGN#pragma pack(push,1)
#endif/**
* 
* 0 1   3     6   8 9            15
* +-+---+-----+---+-+-------------+
* | |   |     |   | |             |
* |a| b |  c  | d |e|     pad     |
* | |   |     |   | |             |
* +-+---+-----+---+-+-------------+
*
*/
struct Info {uint16_t a : 1;// uint16_t :0; // 将 uint16_t 所占用两个字节的剩余空间(15个位)全部填0,下个变量从第三个字节开始(实测)uint16_t b : 2;uint16_t c : 3;// uint16_t c1 : 15; // 如果本成员过大,上个位域共享空间不足以存储本成员,本成员会新起一个空间开始存储// uint8_t cc : 3; // 类型改变,不再和上一位域共享一个 uint16_t 空间(2个字节),跳过上个空间开始存储uint16_t d : 2;uint16_t e : 1;uint16_t pad : 7;
} ONEBYTE_ALIGN;#if defined(__GNUC__) || defined(__GNUG__)#undef ONEBYTE_ALIGN
#elif defined(_MSC_VER)#pragma pack(pop)#undef ONEBYTE_ALIGN
#endifstd::cout << sizeof(Info) << std::endl;   // 2
std::cout << alignof(Info) << std::endl;  // 1

本文部分摘录自:阿秀

声明:本文使用八爪鱼rpa工具从gitee自动搬运本人原创(或摘录,会备注出处)博客,如版式错乱请评论私信,如情况紧急或久未回复请致邮 xkm.0jiejie0@qq.com 并备注原委;引用本人笔记的链接正常情况下均可访问,如打不开请查看该链接末尾的笔记标题(右击链接文本,点击 复制链接地址,在文本编辑工具粘贴查看,也可在搜索框粘贴后直接编辑然后搜索),在本人博客手动搜索该标题即可;如遇任何问题,或有更佳方案,欢迎与我沟通!


http://www.mrgr.cn/news/60027.html

相关文章:

  • 重构代码之状态与策略模式
  • 【STM32+HAL】OV2640捕获图像显示
  • qt QMediaPlaylist
  • 第十八届联合国世界旅游组织/亚太旅游协会旅游趋势与展望大会在广西桂林开幕
  • dy a_bogus 1.0.1.17 最新版本补环境 分析
  • css设置滚动条样式
  • Semantic Kernel进阶:创建和管理聊天(ChatCompletion)历史记录对象(四)
  • Linux:认识文件
  • PCL 基于法向量夹角提出错误匹配点对
  • shodan4,挂黑网站查找,弱口令网站搜索
  • 图---java---黑马
  • 【H2O2|全栈】CSS案例章节(一)——圣杯布局和双飞翼布局
  • spring boot 整合Knife4j
  • 【最新】Kali Linux虚拟机安装与优化全攻略:必做设置让你事半功倍!
  • python print常见用法
  • 【鸿蒙开发 | 端云一体化 —— 开发app不要在为没有后端而烦恼了,端云一体化帮你完成一站式开发!】
  • 自动化结账测试:使用 Playwright确保电商支付流程的无缝体验【nodejs]
  • 【力扣】[Java版] 刷题笔记-101. 对称二叉树
  • MATLAB生物细胞瞬态滞后随机建模定量分析
  • 基础设施即代码(IaC):自动化基础设施管理的未来
  • ctfshow——web(持续更新)
  • 指数运算和幂运算
  • redis详细教程(3.hash和set类型)
  • 计算机网络——有连接传输层协议TCP
  • java平方根计算 C语言指针变量
  • UICC运营商特权与eSIM配置文件冲突问题