C++11新特性:aligned_storage等空间分配工具

C++11对于内存对齐的支持

        对齐的数据有助于提高内存的访问效率以及减少程序运行期间因为内存未对齐导致硬件抛出错误的可能。因此在c++中,数据的对齐是必不可少的,对于系统而言在默认情况下也是坚持数据对齐这一准则的。关于内存对齐的详细内容可见《C++ 内存对齐》。

        在旧式的c++语言中,我们可以通过sizeof关键字查看数据的长度,但却无法对数据的内存对齐的方式进行查询,以及执行相关的对齐操作。这使得当程序涉及有关数据对齐的一些特性时将会变得异常困难。

        而在c++11新标准中,为了支持内存对齐的操作,引入了alignofalignas等关键字。同时也引入了基于内存对齐的存储空间管理工具:

  • std::aligned_storage
  • std::aligned_union

        这两个工具均定义在头文件<type_traits>中,接下来就来详细谈谈这两个工具。


std::aligned_storage

  std::aligned_storage 是 C++11 引入的一个模板结构,用于创建具有特定大小和对齐要求的未初始化存储空间。它主要用于需要手动管理内存对齐的场景,确保在使用某些类型时不会出现对齐问题。 其语法如下:  

template <std::size_t Len, std::size_t Align = alignof(std::max_align_t)>
struct aligned_storage;
  • Len:所要分配的存储空间的大小(以字节为单位)。
  • Align:存储空间的对齐要求(以字节为单位)。其默认值为std::max_align_t

什么是std::max_align_t

  std::max_align_t是对齐值可能的最大值,也就是该值将会满足所有数据类型的对齐要求。该值是由编译器和系统共同决定的。  

        例如,在 x86-64 平台上,最大对齐值通常是 16 个字节,因为 long double 类型的对齐值是 16 个字节。在 ARM 平台上,最大对齐值可能会是 8 个字节,因为 double 类型的对齐值是 8 个字节。

        例如,在我的平台上,最大对齐值为8字节:

  #include <cstddef>  //max_align_t定义在此头文件内std::cout << "Alignas:" << alignof(std::max_align_t) << std::endl;--Output:8

        使用如下语句可以得到一个类型:

 std::aligned_storage<20,4>::type  //注意,这是一个类型

上述语句定义了一个20字节为大小,4字节对齐(地址为4的倍数)的内存块类型,使用该类型可以在堆空间或栈空间上分配该内存块。

  {std::aligned_storage<20,4>::type MyBlock;  //栈上开辟内存块std::cout << "The Address:" << &MyBlock << std::endl;std::cout << "The Size:" << sizeof(MyBlock) << std::endl;}--Output:The Address:3D2A4FF7C4The Size:20

        上述用例在栈上开辟了一块大小为20字节,以4字节对齐的内存块,在当前作用域有效,离开作用域后资源被自动回收

  {typedef std::aligned_storage<20,4>::type blockCustom;auto *ptr_to_block = new blockCustom;  //堆上开辟内存块std::cout << "The Address:" << ptr_to_block << std::endl;std::cout << "The Size:" << sizeof(*ptr_to_block) << std::endl;delete ptr_to_block;}--Output:The Address:17F659AF9E0The Size:20

        上述用例在堆上开辟了一块大小为20字节,以4字节对齐的内存块,应注意其生命周期的管理。

        在使用storageunion工具时,应使用typedefusing(C11)为其冗长的类型取一个清晰易读的别名,方便后续的使用以及使代码的可读性更好:

  {typedef std::aligned_storage<sizeof(Entity),alignof(Entity)>::type blockCustom;using _blockCustom = std::aligned_storage<sizeof(Entity),alignof(Entity)>::type;blockCustom storage_1;  //别名创建方便快捷}

        开辟出来的空间是未被初始化的,想要在其中构造对象应该使用 placement new

class Entity {public:Entity() { std::cout << "Constructor called" << std::endl; }~Entity() { std::cout << "Destructor called" << std::endl; }private:int a;double b;char c;
};void test() {{using _blockCustom = std::aligned_storage<40>::type;_blockCustom storage;  //栈上开辟内存块auto *ptr_to_ent = new (&storage) Entity();  //Placement New}
}
--Output:
Constructor called

        可以看到,我们使用Placement New成功在该内存块中构造了一个Entity对象。有朋友可能已经发现,我们析构函数并没有被调用,虽然我们的内存块是开辟在栈空间中的,可以在作用域离开时自动回收内存资源,但是这么做并不好。

        假如Entity的析构函数中需要释放一些其余动态资源,析构函数不被调用则很可能会导致未定义行为,因此我们必须保证析构函数被手动调用

 ptr_to_ent->~Entity();

亦或是你也可以使用智能指针进行管理

  {using _blockCustom = std::aligned_storage<40>::type;auto ptr_to_block = new _blockCustom;  //堆上开辟内存块std::unique_ptr<Entity> smart_ptr_ent(new (ptr_to_block) Entity());}--Output:Constructor calledDestructor called

注意!

        使用智能指针管理的方法只能用在堆空间上,不能用在栈空间上!因为智能指针会尝试调用 delete,而这对于栈上分配的内存是不适用的,会导致未定义行为。例如下列代码,是无法正常运行的。如果必须要用则应给定自定义的删除器!(详见《C++11 智能指针》)

  {using _blockCustom = std::aligned_storage<40>::type;_blockCustom storage;  //栈上开辟内存块std::unique_ptr<Entity> smart_ptr_ent(new (&storage) Entity());  //不能用智能指针!}

        当然,你也可以使用c++中封装好的allocator(空间配置器)来在所分配的内存块上创建对象。如下所示:

 {using _blockCustom = std::aligned_storage<40>::type;_blockCustom storage;  //栈上开辟内存块std::allocator<Entity> alloc;alloc.construct((Entity *)&storage);alloc.destroy((Entity *)&storage); }
--Output:
Constructor called
Destructor called

总而言之:

  • std::aligned_storage是一个在C++11标准中引入的,支持内存对齐的内存分配工具。
  • 它提供了一种灵活的方式来创建未初始化的存储空间,并允许在其中构造对象。
  • 所分配的存储空间是未初始化的,因此在使用之前需要显式构造对象。
  • 其只提供存储空间,不会自动管理对象的生命周期,因此需要手动调用构造和析构函数 (注意:若内存分配在栈空间上则不建议使用智能指针管理该空间中的对象,若仍要使用请务必自定义删除器)。
  • 确保提供的大小和对齐要求是正确的,以避免未定义行为。

    该工具的其中一个应用场景是配合其它工具,实现类型擦除技术。所谓类型擦除技术,是一种编程技术,用于在编译时去掉类型信息,从而实现不同类型的对象在内存中共享相同的布局和大小

struct Any {static constexpr size_t alloc_size = 64;static constexpr size_t alignment = alignof(std::max_align_t);typedef std::aligned_storage<alloc_size,alignment>::type block_type;block_type storage;template<typename T>void set(T &&value) {new (&storage) T(std::forward<T>(value));}template<typename T>T* get() {return reinterpret_cast<T *>(&storage);}
};void myfunc() {Any any;any.set(5.44);std::cout << *any.get<double>() << std::endl;any.set(std::string("Hello,Cplusplus"));std::cout << *any.get<std::string>() << std::endl;
}

        正如上述用例所示:Any 类提供了一种在固定大小的内存块中存储任意类型对象的机制。我们通过aligned_storage工具配合placement new以及完美转发技术实现了一个最为简单的类型擦除应用的示例。  


std::aligned_storage_t

  std::aligned_storage_t 是 C++11 中引入的 std::aligned_storage 的简化版本,专门用于生成一个具有指定大小和对齐要求的类型别名。与 std::aligned_storage 不同的是,它省去了显式访问::type 的麻烦,从而提高了代码的可读性和简洁性(其实就是使用using封装了一下) 。

        从C++14之后,std::aligned_storage_t就被定义为了一个类型别名。其语法原型如下:  

template <std::size_t Len, std::size_t Align = default>
using std::aligned_storage_t = typename std::aligned_storage<Len, Align>::type;

这个别名直接生成一个满足 Len 和 Align 要求的类型,简化了对 std::aligned_storage 的使用。

  {std::aligned_storage_t<16,alignof(Entity)> storage;  //栈上开辟内存块typedef std::aligned_storage_t<16,alignof(Entity)> blockType;  //再封装一层blockType storage;}

如上述用例,相比起原来确实更加直观,简洁。但是在使用时仍然建议再封装一层。


std::aligned_union

  std::aligned_union` 是从 C++11 引入的,用于计算满足对齐要求的内存块大小和对齐方式,该内存块可以用来存储任意一种指定的类型 。 这是一个类模板,会自动计算所需的对齐和存储大小,以便满足多个类型的存储需求 。其语法定义如下:

template<std::size_t Len, typename... Types>
struct std::aligned_union;
  • Len : 该联合体的最小存储大小。
  • Types : 是一组类型,这些类型是将被联合存储的可能选项。

 类成员:

  • type : 一个类型别名,表示满足所有对齐和大小需求的联合体 (也就是获取类型)。
  • alignment_value : 一个 static constexpr 值,表示满足所有类型对齐需求的对齐值
  • __strictest 是一个类型,它是所有给定类型的最严格对齐要求的交集。也就是说,__strictest 是一个类型,它的对齐方式和大小都满足给定类型的最严格要求。这里只做简单了解。
  • _s_len 是一个内部方法,用于获取 std::aligned_union 的大小。同样这里不深入展开。

        来看下面一个例子:

class A {public:A() { std::cout << "A Consturct." << std::endl; }~A(){}int a;char b;double c;
};class B {public:B() { std::cout << "B Consturct." << std::endl; }~B(){}int* a;char b;double c;
};{typedef std::aligned_union<0,A,B>::type MyUnion;MyUnion storage;auto* ptr_to_A = new(&storage) A();ptr_to_A->~A();  //手动调用析构auto* ptr_to_B = new(&storage) B();ptr_to_B->~B();}

        上述用例中,我们通过aligned_union工具,设置了自己的一个共用体类型:上述代码中将最小存储大小设置为0,当所设置的最小存储大小小于最大对象的内存时,根据计算式max(sizeof(A),sizeof(B)),会取能容纳一个在所给出的类型中(上述为A,B类型中)占用内存空间最大对象的内存来作为该内存块的内存。并且根据计算式max(alignof(A), alignof(B))确定内存块的对齐方式。

        根据内存对齐的原则,我们可以知道:sizeof(A) = 16, sizeof(B) = 24alignof(A) = alignof(B) = 8(8字节对齐)。因此可以确定MyUnion大小为24字节,对齐方式为8字节对齐。

        所以上述在栈上所分配的内存块(storage)就可以用于存储一个A对象,或存储一个B对象。但是注意,以下这种写法是不可取的!

 {typedef std::aligned_union<0,A,B>::type MyUnion;MyUnion storage;char* flag = reinterpret_cast<char *>(&storage);auto* ptr_to_A = new(&storage) A();flag += sizeof(A);  //指针偏移auto* ptr_to_B = new(flag) B();ptr_to_A->~A();  //手动调用析构ptr_to_B->~B();}

   storage 是一个 MyUnion 对象,大小为 24 字节,实际只能容纳一个 A 或一个 B 对象。偏移 16 字节后构造B对象会导致A和B在同一个内存块中重叠

   A 使用了 storage 的前 16 字节。B 使用了从第 16 字节到第 24 字节的内存,这不仅会导致 B 的数据部分超出 storage 范围,产生内存溢出,同时,如果 flag 的地址不满足 B 的对齐需求,可能会触发未定义行为,而且,A 和 B 同时存在,析构时如果 A 和 B 的内存范围重叠,析构函数可能破坏彼此的数据。

        为了避免内存重叠的问题,std::aligned_union 分配的内存块通常只用于存储一个对象。该工具可用于在内存池中向用户分配内存块。

        关于std::aligned_union_t,其用法与std::aligned_storage_t一致,是一个简化了的版本,此处仅给出如下示例:

 {typedef std::aligned_union_t<0,A,B> MyUnion;MyUnion alloc_union;auto *ptr_to_A = new(&alloc_union) A();ptr_to_A->~A();}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

3D滤波器处理遥感tif图像

import cv2 import numpy as np from osgeo import gdal# 定义 Gabor 滤波器的参数 kSize 31 # 滤波器核的大小 g_sigma 3.0 # 高斯包络的标准差 g_theta np.pi / 4 # Gabor 函数的方向 g_lambda 10.0 # 正弦波的波长 g_gamma 0.5 # 空间纵横比 g_psi np.pi / 2 # …

UnityXR Interaction Toolkit 如何检测HandGestures

前言 随着VR设备的不断发展,从最初的手柄操作,逐渐演变出了手部交互,即头显可以直接识别玩家的手部动作,来完成手柄的交互功能。我们今天就来介绍下如何使用Unity的XR Interaction Toolkit 来检测手势Hand Gesture。 环境配置 1.使用Unity 2021或者更高版本,创建一个项…

Unity Protobuf实践

官方文档&#xff1a;https://protobuf.com.cn/overview/ 1. 获取Protobuf&#xff1a; 1.1 通过NuGet包管理器&#xff1a; 拷贝dll&#xff1a; 选择.net2.0的dll&#xff1a; 导入Unity Plugins目录&#xff1a; 1.2 下载源码并生成dll&#xff1a; GitHub - protocolbuf…

【微服务】面试 4、限流

微服务限流技术总结 一、微服务业务面试题引入 在微服务业务面试中&#xff0c;限流是重要考点&#xff0c;常与分布式事务、分布式服务接口幂等解决方案、分布式任务调度等一同被考查。面试官一般会询问项目中是否实施限流及具体做法&#xff0c;回答需涵盖限流原因、采用的方…

VScode 配置 C语言环境

遇到的问题集合 mingw官方下载网站&#xff08;https://sourceforge.net/projects/mingw-w64/files/&#xff09;更新之后&#xff0c;与网上大多数教程上写的界面不同了。 网上大多数教程让下载这个&#xff1a; 但是现在找不到这个文件。 写hello.c文件时&#xff0c;报错&…

语音技术与人工智能:智能语音交互的多场景应用探索

引言 近年来&#xff0c;智能语音技术取得了飞速发展&#xff0c;逐渐渗透到日常生活和各行各业中。从语音助手到智能家居控制&#xff0c;再到企业客服和教育辅导&#xff0c;语音交互正以前所未有的速度改变着人机沟通的方式。这一变革背后&#xff0c;人工智能技术无疑是关键…

26个开源Agent开发框架调研总结(2)

根据Markets & Markets的预测&#xff0c;到2030年&#xff0c;AI Agent的市场规模将从2024年的50亿美元激增至470亿美元&#xff0c;年均复合增长率为44.8%。 Gartner预计到2028年&#xff0c;至少15%的日常工作决策将由AI Agent自主完成&#xff0c;AI Agent在企业应用中…

IOS HTTPS代理抓包工具使用教程

打开抓包软件 在设备列表中选择要抓包的 设备&#xff0c;然后选择功能区域中的 HTTPS代理抓包。根据弹出的提示按照配置文件和设置手机代理。如果是本机则会自动配置&#xff0c;只需要按照提醒操作即可。 iOS 抓包准备 通过 USB 将 iOS 设备连接到电脑&#xff0c;设备需解…

Java面试核心知识4

公平锁与非公平锁 公平锁&#xff08;Fair&#xff09; 加锁前检查是否有排队等待的线程&#xff0c;优先排队等待的线程&#xff0c;先来先得 非公平锁&#xff08;Nonfair&#xff09; 加锁时不考虑排队等待问题&#xff0c;直接尝试获取锁&#xff0c;获取不到自动到队尾…

在 Linux 下Ubuntu创建同权限用户

我是因为不小心把最开始创建的用户的文件夹颜色搞没了&#xff0c;再后来全白用习惯了&#xff0c;就不想卸载了&#xff0c;像创建一个和最开始创建的用户有一样的权限可以执行sudo -i进入root一样的用户 如图这是最原始的样子 第一步 创建新用户&#xff0c;我这里是因为之前…

【Unity插件】解决移动端UI安全区问题 - Safe Area Helper

在移动端设计界面时&#xff0c;必须要考虑的就是UI的安全区。 Unity本身也提供了Safearea的API。 但在asset store时已经有人提供了免费的插件&#xff08;Safe Area Helper&#xff09;&#xff0c;我们可以直接使用。 插件链接&#xff1a; https://assetstore.unity.com/p…

机器学习之随机森林算法实现和特征重要性排名可视化

随机森林算法实现和特征重要性排名可视化 目录 随机森林算法实现和特征重要性排名可视化1 随机森林算法1.1 概念1.2 主要特点1.3 优缺点1.4 步骤1.5 函数及参数1.5.1 函数导入1.5.2 参数 1.6 特征重要性排名 2 实际代码测试 1 随机森林算法 1.1 概念 是一种基于树模型的集成学…

OpenAI 故障复盘 - 阿里云容器服务与可观测产品如何保障大规模 K8s 集群稳定性

本文作者&#xff1a; 容器服务团队&#xff1a;刘佳旭、冯诗淳 可观测团队&#xff1a;竺夏栋、麻嘉豪、隋吉智 一、前言 Kubernetes(K8s)架构已经是当今 IT 架构的主流与事实标准&#xff08;CNCF Survey[1]&#xff09;。随着承接的业务规模越来越大&#xff0c;用户也在使…

SpringBoot 使用 Cache 集成 Redis做缓存保姆教程

1. 项目背景 Spring Cache是Spring框架提供的一个缓存抽象层&#xff0c;它简化了缓存的使用和管理。Spring Cache默认使用服务器内存&#xff0c;并无法控制缓存时长&#xff0c;查找缓存中的数据比较麻烦。 因此Spring Cache支持将缓存数据集成到各种缓存中间件中。本文已常…

MySQL —— 在CentOS9下安装MySQL

MySQL —— 在CentOS9下安装MySQL 1.查看自己操作系统的版本2.找到对应的安装源3.上传我们在windows下&#xff0c;下载的文件&#xff0c;解压4.执行rpm命令&#xff0c;启用MySQL8仓库5.执行dnf install -y mysql-community-server6.设置开机自启动7.获得初始密码8.登录MySQL…

Center Loss 和 ArcFace Loss 笔记

一、Center Loss 1. 定义 Center Loss 旨在最小化类内特征的离散程度&#xff0c;通过约束样本特征与其类别中心之间的距离&#xff0c;提高类内特征的聚合性。 2. 公式 对于样本 xi​ 和其类别yi​&#xff0c;Center Loss 的公式为&#xff1a; xi​: 当前样本的特征向量&…

AI在软件工程教育中的应用与前景展望

引言 随着科技的快速发展&#xff0c;软件工程教育面临着前所未有的挑战与机遇。传统的教学模式逐渐无法满足快速变化的行业需求&#xff0c;学生们需要更多的实践经验和个性化的学习方式。而在这样的背景下&#xff0c;人工智能&#xff08;AI&#xff09;作为一项创新技术&a…

【微服务】面试 7、幂等性

幂等性概念及场景 概念&#xff1a;多次调用方法或接口不改变业务状态&#xff0c;重复调用结果与单次调用一致。例如在京东下单&#xff0c;多次点击提交订单只能成功一次。场景&#xff1a;包括用户重复点击、网络波动导致多次请求、mq 消息重复消费、代码中设置失败或超时重…

Redis 为什么要引入 Pipeline机制?

在 Redis 中有一种 Pipeline&#xff08;管道&#xff09;机制&#xff0c;其目的是提高数据传输效率和吞吐量。那么&#xff0c;Pipeline是如何工作的&#xff1f;它又是如何提高性能的&#xff1f;Pipeline有什么优缺点&#xff1f;我们该如何使用 Pipeline&#xff1f; 1、…

游戏引擎学习第78天

Blackboard: Position ! Collision “网格” 昨天想到的一个点&#xff0c;可能本来就应该想到&#xff0c;但有时反而不立即思考这些问题也能带来一些好处。节目是周期性的&#xff0c;每天不需要全程关注&#xff0c;通常只是在晚上思考&#xff0c;因此有时我们可能不能那么…