C++之数据对齐

目录

  • 关于对齐
  • 数据对齐
  • 结构体对齐原则
  • 原理分析

关于对齐

  • 对齐方式:表示的是一个类型的对象存放的内存地址应满足的条件
  • 好处:对齐的数据在读写上有性能优势
  • 对于不对齐的结构体,编译器会自动补齐以提高CPU的寻址效率

数据对齐

  • 四个函数/描述符
    • offsetof(X, x):X为结构体名(也可为对象名,实测对象名是不可以的),x为结构体成员,返回x在X中的偏移(以0开始),按字节的,按位是不行的——带虚函数的类计算不了偏移?
      • offsetof的实现:#define offset(s, m)((size_t)&reinterpret_cast<char const volatile&>(((s*)0)->m))
      • 你可能会迷惑,这样强制转换后的结构指针怎么可以用来访问结构体字段?呵呵,其实这个表达式根本没有也不打算访问m字段。ANSI C标准允许任何值为0的常量被强制转换成任何一种类型的指针,并且转换结果是一个NULL指针,因此((s*)0)的结果就是一个类型为s的NULL指针。如果利用这个NULL指针来访问s的成员当然是非法的,但&(((s)0)->m)的意图并非想存取s字段内容,而仅仅是计算当结构体实例的首址为((s*)0)时m字段的地址。聪明的编译器根本就不生成访问m的代码,而仅仅是根据s的内存布局和结构体实例首址在编译期计算这个(常量)地址,这样就完全避免了通过NULL指针访问内存的问题。又因为首址的值为0,所以这个地址的值就是字段相对于结构体基址的偏移。
    • alignof(X):查询X的对齐字节,由最大元素类型(,基础类型,如果有嵌套结构体,取该结构体中的最大基础类型比较)的字节数决定;数组的对齐值由其元素决定;如alignof(char)就是1
      • 参数X:自定义类型或内置类型或者变量,不完整类型编译不过
      • 返回std::size_t类型值
      • __alignof是Microsoft的运算符
      • 引用与其引用的数据b对齐值相同
    • 对齐描述符alignas:重新设定结构体的对齐方式;既可以接收常量表达式,也可以接受类型作为参数;
      • 使用常量表达式作为alignas的操作符时结果必须是以2的幂次作为对齐值,设定的对齐值如果小于默认对齐则会忽视
      • C++11标准中规定了一个“基本对齐值”,一般情况下等于平台支持的最大标量数据类型的对齐值,可以通过alignof(std::max_align_t)来查询其值 (#include)
      • 在C++11之前,使用编译器的扩展来描述对齐方式,如gnu的__attribute__((aligned(8)))
    • C++11对于对齐的支持不限于alignof操作符和alignas描述符,STL中的内建函数std::align函数来动态指定的对齐方式调整数据块的位置(待验证)
alignas(x) char c //指定c的对齐是x
struct alignas(x) XX  { A xx; }    // 指定结构体对齐字节为x
  • 通过属性对其(属性是对语言中的实体对象如函数变量等附加的一些额外注解信息,用来实现功能或代码优化):
    • linux:通过GNU的关键字__attribute__来声明:attribute((attribute-list)),详细参考gcc在线文档: https://gcc.gnu.org/onlinedocs/
    • windows:__declspec,如控制变量的对齐方式:__declspec(align(x))
    • C++11的通用属性:[[ attribute_list]]
      • 语法上,通用属性可以作用于类型,变量,名称,代码块等。对于作用于声明的通用属性,既可以写在声明的起始处,也可写在声明的标识符之后;对作用于整个语句的通用属性,应该写在语句起始处
      • 现有C++11标准中只预定义了两个通用属性:
         1. [[noreturn]]:用于标识不会返回的函数(不会返回的函数和返回void不同,不会返回的函数在被调用完成后后续代码不会再被执行),主要用于标识那些不会将控制流返回给原调用函数的函数,如终止应用程序的函数,异常抛出函数等,好处有利于编译器进行优化,但要谨慎使用——待验证
         2. [[carries_dependency]]:和并行情况下的编译期优化有关,主要是用来解决弱内存模型平台上使用memory_order_consume内存顺序枚举问题

结构体对齐原则

  • 结构体成员变量是基本类型:结构体中的第一个成员位置在偏移量0,之后每个变量的偏移量必须是它本身字节数的整数倍;
  • 如果结构体中嵌套结构体,那么嵌套结构体成员的偏移量必须是它最大成员的字节数的整数倍。注意:
     1. 不是按结构体整体的大小偏移;
     2. 不是展开后偏移,整个结构体的偏移量是包括嵌套结构体在内的最大成员的字节数
  • 如果是继承关系,目测和嵌套结构体一样
  • 结构体的总大小为结构的字节边界数(即该结构中占用最大空间的类型所占用的字节数)的整数倍,这样在处理数组时可以保证每一项都边界对齐。,在最后还会根据需要自动填充空缺的字节
  • 强制对齐:编译器提供了#pragma pack(n)来设定变量以n字节对齐方式:
    • 如果n大于等于该变量所占用的字节数,那么偏移量必须满足默认的对齐方式;
    • 如果n小于该变量的类型所占用的字节数,那么偏移量为n的倍数,不用满足默认的对齐方式。
    • 结构的总大小也有个约束条件,分下面两种情况:如果n大于所有成员变量类型所占用的字节数,那么结构的总大小必须为占用空间最大的变量占用的空间数的倍数;否则必须为n的倍数。
    • 注意:这个预编译命令会不论大小都会改变对齐值,alignas只会改大不会改小
#pragma pack(push)         //保存对齐状态
#pragma pack(4)                //设定为4字节对齐,n=1,2,4,8,16改变系统的对齐系数
struct test
{
char m1;
double m4;
int m3;
};
#pragma pack(pop)            //恢复对齐状态

验证结果:

  • 在linux下测试都有效;
  • 单独使用#pragma pack(x)在vs下测试会使后面定义的结构体都按x对齐;

原理分析

C语言,包括其他的编程语言都会有内存对齐机制,否则编译出来的软件无法正常运行。在内存中,所有的数据都是按字节为最小单位存储的,存储单位称为存储单元,所以也叫内存是由很多存储单元组成的,这些存储单元(字节)都有固定的地址标示着(这里说的非虚拟模式下),在我们程序员眼里内存就是一个一个字节组成的,这些字节对应的地址都是连续并排好的,但是在CPU中不是,在CPU看来内存是一段一段的,每段大小取决于CPU的寻址位宽!
比如在C语言里申请了short和两个char变量:

Shor a;
Char b;
Char c;

在内存中对应的起始地址为:0x01(a),0x03(b),0x04©
在程序员眼里在内存中的存储是这样的:
在这里插入图片描述

但是在一个寻址位宽为32位的CPU上是这样的:
在这里插入图片描述

Short占用16个bit位char占用8个bit位所以16+8+8=32 刚好组成一段

但是如果在c语言中我们这样定义了三个变量:

Short a;(起始地址:0x01)
Char b;(起始地址:0x03)
Short c;(起始地址:0x04)

那么如果C语言编译器不为我们自动对齐在内存中就会这样:
在这里插入图片描述
可以看到最后一个short的地址被分两个段存储了,也就是说如果CPU要读取变量c那么必须先将第一个段里的数据全部读取出来送到寄存器里(这里假定通用寄存器为32位),然后将第一个寄存器里的0x01到0x03的数据剔除掉,将0x04上的bit位数据挪移到寄存器的低位上,然后在去读取第二个段上的地址数据也送到寄存器里,然后将0x06到0x08上的数据剔除掉,这样就将额外的数据剔除掉了,但是这样就会读取两次,浪费更多的时间1,为什么不直接从0x04开始读取?注意CPU不可以从某段的段偏移上读取只能从某段的起始地址开始读取!这是虚拟内存中的概念(详细参见虚拟内存的映射关系),不然的话就会造成读取时出现数据越界的情况!

假如0x05地址是栈的尾地址,如: 0x04 0x05 0x06 0x07
那么在一个32位的CPU下寻址时就会发生越界的情况,访问到其他进程下的内存则会被操作系统里内核中断代码捕捉会被视为恶意代码会立即被中断,当然部分CPU也并不会全部都给你按上面的方式来读取,假如遇到了上图那样的地址存储方式,CPU会直接报硬件中断,直接罢工不读取,然后由操作系统捕捉这个硬件中断直接咔擦掉你的程序!在调试情况下调试器捕捉这个消息时会中断!
所以为了解决这一问题,编译器使用了一种叫做空间交换法的方式来对齐内存,也就是说增加额外的内存,换取时间上的效率,同时也避免部分CPU的硬件中断!
下面是C语言内存对齐后的地址:
在这里插入图片描述

可以看到char为了对齐short增加了一个字节,而short为了对齐32位寻址增加了两个字节,这就是空间上的时间交换法,这样的话CPU读取时只需要一次就可以读取完!注意增加的字节只是为了对齐寻址,额外的字节是不允许被赋值的,虽然说可以赋值,但是当我们赋值是编译器不会允许我们向额外的地址传递数据!注意以上内存对齐是发生在结构体当中的,不光是结构体,在函数体里所有的变量包括代码段内存全部都会被内存对齐,函数体里的对齐方式不同于结构体,博主也没有去深刻的去查函数体里的对齐方式,这里也不必要做过多的了解因为编译器都会帮我们做好!只是在个别面试时会面试到关于内存对齐的基础题!

这种方法就是空间上的时间转换,牺牲额外的空间换取时间上的效率,毕竟在这个内存越来越大,价格越来越便宜的年代,牺牲额外的内存换取效率也是一件可行之事!

每个编译器里都有一个内存对齐的模数,这个摸数取决于你的CPU位宽!我们可以通过# #pragma pack(n)来强制改变它,n的可取值范围是:n=1,2,4,8,16(字节),当然这里也不是特别建议去改变它的默认寻址宽度,因为超出的话CPU没有办法一次性读取完还是会分段裁剪来读,过小的话CPU也是要去分两次或者更多的次数然后裁剪的读取,所以建议为默认值

以上原理分析部分转发自博客:https://blog.csdn.net/bjbz_cxy/article/details/78418828

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/18325.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

机器学习实战:带你进入AI世界!

机器学习是人工智能领域的一个重要分支&#xff0c;可以帮助我们从大量数据中发现规律&#xff0c;进行预测和分类等任务。然而&#xff0c;想要真正掌握机器学习算法&#xff0c;并将其应用到实际问题中&#xff0c;还需要进行大量的实战练习。 本文将介绍几个常见的机器学习实…

6、Flutterr聊天界面网络请求

一、准备网络数据 1.1 数据准备工作 来到网络数据制造的网址,注册登录后,新建仓库,名为WeChat_flutter;点击进入该仓库,删掉左侧的示例接口,新建接口. 3. 接着点击右上角‘编辑’按钮,新建响应内容,类型为Array,一次生成50条 4. 点击chat_list左侧添加按钮,新建chat_list中的…

PAT A1032 Sharing

1032 Sharing 分数 25 作者 CHEN, Yue 单位 浙江大学 To store English words, one method is to use linked lists and store a word letter by letter. To save some space, we may let the words share the same sublist if they share the same suffix. For example, l…

如何利用ChatGPT进行论文润色-ChatGPT润色文章怎么样

ChatGPT润色文章怎么样&#xff1f; ChatGPT可以润色文章&#xff0c;使用其润色功能可以为用户提供更加整洁、清晰、文采动人的文本。但需要注意以下几点&#xff1a; 需要保持文本的一致性和完整性。当使用ChatGPT进行润色时&#xff0c;需要注意保持文本的一致性和完整性。…

和月薪5W的聊过后,才发现自己一直在打杂···

前几天和一个朋友聊面试&#xff0c;他说上个月同时拿到了腾讯和阿里的offer&#xff0c;最后选择了阿里。 我了解了下他的面试过程&#xff0c;就一点&#xff0c;不管是阿里还是腾讯的面试&#xff0c;这个级别的程序员&#xff0c;都会考察项目管理能力&#xff0c;并且权重…

Java程序设计入门教程---循环结构(while)

目录 思考 概念 语法 案例&#xff1a;求1到100的整数和&#xff1f; 案例分析 思考 1. 让你输出10000000000000000句“Hello,world!”&#xff0c;你怎么写代码&#xff1f; 2. 求1到100的整数和&#xff1f; 概念 循环结构程序多次循环执行相同或相近的任务。 while循环…

Windows在外远程桌面控制macOS 【macOS自带VNC远程】

文章目录 前言1.测试局域网内远程控制1.1 macOS打开屏幕共享1.2 测试局域网内VNC远程控制 2. 测试公网远程控制2.1 macOS安装配置cpolar内网穿透2.2 创建tcp隧道&#xff0c;指向5900端口 3. 测试公网远程控制4. 配置公网固定TCP地址4.1 保留固定TCP地址4.2 配置固定TCP端口地址…

什么?Python一行命令快速搭建HTTP服务器并公网访问?

文章目录 1.前言2.本地http服务器搭建2.1.Python的安装和设置2.2.Python服务器设置和测试 3.cpolar的安装和注册3.1 Cpolar云端设置3.2 Cpolar本地设置 4.公网访问测试5.结语 转载自远程内网穿透的文章&#xff1a;【Python】快速简单搭建HTTP服务器并公网访问「cpolar内网穿透…

springboot第19集:权限

article 文章表sys_permission 后台权限表sys_role 后台角色表sys_role_permission 角色-权限关联表sys_user 用户表sys_user_role 用户-角色关联表 image.png image.png sys_user_role id user_id(用户id) role_id(角色id) sys_role id role_name(角色名) create_time(创建时间…

基于 EKS Fargate 搭建微服务性能分析系统

背景 近期 Amazon Fargate 在中国区正式落地&#xff0c;因 Fargate 使用 Serverless 架构&#xff0c;更加适合对性能要求不敏感的服务使用&#xff0c;Pyroscope 是一款基于 Golang 开发的应用程序性能分析工具&#xff0c;Pyroscope 的服务端为无状态服务且性能要求不敏感&…

部署simple-chat项目

simple-chat介绍&#xff1a;此项目是基于openAI3.5模型的h5端人工智能聊天项目&#xff0c;无需翻墙即可体验。 simple-chat线上地址&#xff1a;simple-chat simple-chat项目地址&#xff1a;GitHub - AMxiaoming/simple-chat nginx部署前端步骤&#xff1a; https://blo…

MySQL基础(十八)MySQL8其它新特性

1. MySQL8新特性概述 MySQL从5.7版本直接跳跃发布了8.0版本&#xff0c;可见这是一个令人兴奋的里程碑版本。MySQL 8版本在功能上做了显著的改进与增强&#xff0c;开发者对MySQL的源代码进行了重构&#xff0c;最突出的一点是MySQL Optimizer优化器进行了改进。不仅在速度上得…

HashSet和HashMap内部结构分析

首先明确一点&#xff1a;HashSet的底层就是HashMap HashSet与HashMap的不同点&#xff1a; HashMap存储的是键值对&#xff08;也就是key-value&#xff09;&#xff0c;即在调用HashMap的put方法时传入的两个值&#xff0c;而HashSet其实也是存储的键值对&#xff0c;但是键…

阿里云服务器镜像怎么选?操作系统版本选择说明

阿里云服务器镜像怎么选择&#xff1f;云服务器操作系统镜像分为Linux和Windows两大类&#xff0c;Linux可以选择Alibaba Cloud Linux&#xff0c;Windows可以选择Windows Server 2022数据中心版64位中文版&#xff0c;阿里云百科来详细说下阿里云服务器操作系统有哪些&#xf…

【sop】基于灵敏度分析的有源配电网智能软开关优化配置(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

从爆火的“哇呀挖”,思考我软件开发的人生意义何在?

【 在什么样的花园里面&#xff0c;挖呀挖呀挖&#xff0c;种什么样的种子&#xff0c;开什么样的花&#xff0c;在小小的花园里面&#xff0c;挖呀挖呀挖&#xff0c;种小小的种子&#xff0c;开小小的花&#xff0c;在大大的花园里面&#xff0c;挖呀挖呀挖&#xff0c;种大大…

国内GPU渲染农场有哪些值得推荐?

GPU凭借它在图形渲染领域强大的架构和计算能力&#xff0c;给广大用户带来了一种更为高效的解决方案&#xff0c;我们启用GPU渲染加速&#xff0c;实际就是调用GPU加速图形的渲染和填充。既然聊到GPU渲染&#xff0c;CG行业的朋友们肯定也好奇国内值得推荐的GPU渲染农场有哪些&…

​射频PCB 设计​的六大条技巧

即使是最自信的设计人员&#xff0c;对于射频电路也往往望而却步&#xff0c;因为它会带来巨大的设计挑战&#xff0c;并且需要专业的设计和分析工具。这里将为您介绍六条技巧&#xff0c;来帮助您简化任何射频PCB 设计任务和减轻工作压力&#xff01; 1、保持完好、精确的射频…

Android网络代理原理及实现

网络代理简介 代理典型的分为三种类型&#xff1a; 正向代理 缓存服务器使用的代理机制最早是放在客户端一侧的&#xff0c;是代理的原型&#xff0c;称为正向代理。其目的之一 是缓存&#xff0c;另一目的是用来实现防火墙&#xff08;阻止互联网与公司内网之间的包&#x…

第十二章_Redis单线程 VS 多线程

Redis为什么选择单线程&#xff1f; 是什么 这种问法其实并不严谨&#xff0c;为啥这么说呢? Redis的版本很多3.x、4.x、6.x&#xff0c;版本不同架构也是不同的&#xff0c;不限定版本问是否单线程也不太严谨。 1 版本3.x &#xff0c;最早版本&#xff0c;也就是大家口口相…