c++内存管理-内存顺序

C++中的六种内存序详解 C++11中的内存序(Memory Order)为开发者提供了工具,帮助控制多线程程序中不同线程之间的内存操作顺序,从而避免由于编译器或处理器优化导致的指令重排问题。这些内存序提供了从无序到严格一致的内存访问保证,让程序员能够在性能和安全性之间进行权衡。本文将详细介绍C++中的六种内存序,并通过实际例子补充说明它们的应用场景。 阅读详情

转载自:https://www.dazhuanlan.com/2019/12/14/5df40323d6adf/

原子操作(atomic)是无锁编程(Lock-Free Programming)的基础。C++内存模型规定了多个线程访问同一个内存地址时的语义,以及某个线程对内存地址的更新何时能被其它线程看见。

该模型的核心思想就是由程序员用同步原语(例如,锁或者C++11中新引入的atomic类型的共享变量)来保证你程序是没有数据竞争的,这样CPU和编译器就会保证程序是按程序员所想的那样执行的(即顺序一致性)。

C++编译器可能会进行一些错误优化从而导致数据竞争。第二,绝大多数情况下线程库能正确的完成任务,而在极少数对性能有更高要求的情况下(尤其是需要利用底层的硬件特性来实现高性能Lock Free算法时)需要更精确的内存模型以规定好程序的行为

C++11中的内存屏障

松散”内存屏障

std::memory_order_relaxed,松散操作,没有同步或顺序制约,仅对此操作要求原子性。带标签 memory_order_relaxed的原子操作无同步操作;它们不会在并发的内存访问间强加顺序。它们只保证原子性和修改顺序一致性。

例如,对于最初为零的x和y:

 

1

2

3

4

5

6

7

 

r1 = y.load(memory_order_relaxed); // A

x.store(r1, memory_order_relaxed); // B

// 线程 2 :

r2 = x.load(memory_order_relaxed); // C

y.store(42, memory_order_relaxed); // D

允许产生结果r1 == 42 && r2 == 42,因为即使线程1中A先序于B且线程2中C先序于D ,却没有制约避免y的修改顺序中D先出现于A ,而x的修改顺序中B先出现于C。D在y上的副效应,可能可见于线程1中的加载A ,同时B在x上的副效应,可能可见于线程2中的加载C。

如上图所示,假设:a, b, c分别为普通变量,而x为atomic类型的变量,当在写入x时,设置memory_order_relaxed时,写入a,写入b的顺序,在另外的线程上看到的,完全可能是相互调换的,写入c的位置,完全也有可能由出现在写入x之前,而在另外一个线程上看到的,确实在写入x之后,同时,读出b的动作,完全有可能从写入x之后,编程在另外一个线程上看,是在写入x之前。也就是,各种乱序, 都是被允许的。

松散内存顺序的典型使用是计数器自增,只要求原子性,但不要求顺序或同步

单向的”释放”内存屏障

std::memory_order_release,释放操作,当前线程中的读或写不能被重排到此存储后。当前线程的所有写入,可见于加载该同一原子变量的其他线程。

如上图所示,在使用memory_order_release设置的原子操作写入的情况下,原子操作之后的读写操作,在另外的线程(加载了该原子变量的线程)看来,可以重排到原子操作之前(通俗点说,就是另外线程可能认为当前线程是先执行的写入的a操作,再执行的写入x操作)。但是,如下图所示,原子操作之前的读、写操作,计算机系统必须保证,在另外一个线程看来,他是在原子操作写入之前就被写入了, 不能是在原子操作之后才写入(通俗点说,也就是另外一个线程,不能认为当前线程先写入的x,再写入的c,他一定要看到的是先写入c,再写入的x):

单向的”加载”内存屏障

std::memory_order_acquire,加载操作,当前线程中读或写不能被重排到此加载前。其他释放同一原子变量的线程的所有写入,为当前线程所可见。

如上图所示,在设置memory_order_acquire的对x的读操作前的读、写操作,在另外的线程看来(或者说实际的运行顺序),可以被重排到对x的读操作后,(通俗点说,就是其他线程可以是认为当前线程先读取的x,再写入的c)。然而如下图所示,对x的读操作后的读、写操作,在另外的线程看来不允许被重排到x的读操作前(不允许其他线程看到当前线程先写入的a,再读取的x)。

单向“加载”+单向“释放”协议

往往memory_order_acquire和memory_order_release是配合着一起使用的:

  1. 线程1使用memory_order_release写入原子变量x
  2. 线程2使用memory_order_acquire读出原子变量x

所有在线程1上,在写入x之前的写入操作,都将在线程2上,在读出x之后,被看到。使用单向“加载”+单向“释放”协议的场景往往是:

  1. 线程1,写入一些实际数据,接着通过将原子变量x设置为某个值A(通过使用memory_order_release写入原子变量x)来“发布”这些数据。
  2. 线程2,通过读取并判断x已被设置为A(通过使用memory_order_acquire来读取原子变量x),进而读取线程1实际“发布”的那些数据

如上图所示,thread_1在release写入x(值:A)之前,写入了待发布的a,b的数据,而thread_2,将在acquire读出x且为A之后,将读到thread_1发布的a,b的数据。同时,我们可以注意到,在thread_2上,在acquire读出x之前,如果对a进行读操作,我们是无法确认读到的a一定会thread_1在之前最后写入的a,这里的顺序是不会被保证的,重排是被允许的。同时,在之后,读取c,读到的是否为thread_1最后写入的c,也是不确定的,因为,在x写入之后,thread_1上又出现了一次写入,而如果在此之前,还有一次写入, 这两次写入之间,是不存在限制,可能会被重排的。

thread_1上有了release_store,对于a,b的写入就一定会在x的改变之前,在thread_2上,就不会出现类似读出c,的不确定性。thread_2上有了acquire_load,右侧的读出a,就不会被重排读到左侧,而左侧读出a的不确定性,也不存在。thread_1卡住的是:对于数据a,b的写入不能排到x的写入之后,thread_2卡住的,是对于数据a,b的读取,不能排到读取x之前,这样,就保证了数据a,b,与”信号量”x,之间,在thread_1, thread_2上的同步关系。

双向的”加载-释放”内存屏障

std::memory_order_acq_rel,加载-释放操作,带此内存顺序的读-修改-写操作既是获得加载又是释放操作。没有操作能够从此操作之后被重排到此操作之前,也没有操作能够从此操作之前被重排到此操作之后,当然,具有这一限制的前提是,观察读写顺序的不同线程是使用的同一个原子变量并基于这一内存顺序。

最严的”顺序一致”内存屏障

std::memory_order_seq_cst,顺序一致操作,此内存顺序的操作既是加载操作又是释放操作,没有操作能够从此操作之后被重排到此操作之前,也没有操作能够从此操作之前被重排到此操作之后。带标签memory_order_seq_cst的原子操作不仅以与释放/加载顺序相同的方式排序内存(在一个线程中先发生于存储的任何结果都变成做加载的线程中的可见),还对所有拥有此标签的内存操作建立一个单独全序。memory_order_seq_cst比memory_order_acq_rel更强,memory_order_acq_rel的顺序保障,是要基于同一个原子变量的,也就是说,在这个原子变量之前的读写,不能重排到这个原子变量之后,同时这个原子变量之后的读写,也不能重排到这个原子变量之前。但是,如果两个线程基于memory_order_acq_rel使用了两个不同的原子变量x1, x2,那在x1之前的读写,重排到x2之后,是完全可能的,在x1之后的读写,重排到x2之前,也是被允许的。然而,如果两个原子变量x1,x2,是基于memory_order_seq_cst在操作,那么即使是x1之前的读写,也不能被重排到x2之后,x1之后的读写,也不能重排到x2之前,也就说,如果都用memory_order_seq_cst,那么程序代码顺序(Program Order)就将会是你在多个线程上都实际观察到的顺序(Observed Order)

 

 

 

 

 

C++顺序表工业级实现:从内存管理到异常安全的完整指南 顺序表作为线性数据结构的基础,其核心在于连续内存空间的动态管理。理解其工作原理需要掌握内存分配、元素搬移和容量扩展机制,这直接关系到程序的性能和稳定性。在C++工程实践中,容器的价值不仅在于提供数据存储,更在于实现资源的安全管理和高效访问。通过RAII原则确保内存自动释放,利用移动语义优化性能,并设计异常安全的接口来保证操作原子性,这些都是构建健壮系统的关键。本文以工业级顺序表实现为例,深入探讨了如何避免常见的“玩具代码”陷阱,通过手动内存管理、迭代器设计和拷贝控制等实战技巧,帮助开发者夯实C++基础,应对 阅读详情

相关推荐

C++顺序表从零实现:动态数组、深拷贝与内存管理实战

数据结构是计算机科学的核心基础,它定义了数据的组织、存储和操作方式。线性表作为最基本的数据结构之一,其顺序存储实现(顺序表)通过连续的物理内存单元来维持元素的逻辑相邻性,从而实现了高效的随机访问(O(1)时间复杂度)。这一特性使其在需要频繁按索引查询的场景中极具技术价值,例如缓存系统、动态数组及数据库索引的底层实现。然而,物理连续性也带来了插入和删除操作需要移动大量元素的代价(O(n)时间复杂度)。在工程实践中,动态顺序表通过指针在堆上管理内存,并引入扩容策略(如2倍扩容)来平衡性能与空间,其实现涉及深拷贝

weixin_34341117的博客 393

C++ memory order循序渐进(二)—— C++ memory order基本定义和形式化描述所需术语关系详解

目录1.c++的六种memory_ordermemory_order_relaxedmemory_order_consumememory_order_acquirememory_order_releasememory_order_acq_relmemory_order_seq_cstSequenced-beforeCarries dependencyModification orderReleas...

wxj1992的博客 8510

C++11多线程 内存序(std::memory_order_relaxed)

1

qls315的博客 1万+

【Rust 控制流入门指南】 Introduction to Control Flow in Rust

Control flow is a fundamental concept in programming. It determines the order in which the code is executed. In Rust, like in many other programming languages, we have conditional statements and loops to manage the control flow. Let's dive into the first c

探索C++编程的奥秘,分享深入的技术见解和实践,旨在激发读者创造力与解决问题的思维。 569

C++ 的内存顺序模型std::memory_order_relaxed

适用于对数据一致性要求较低、但需要原子性的场景,如计数器、标志检测以及无锁数据结构。使用这种内存顺序模型,可以在保证原子性的同时,避免不必要的同步开销,提升系统性能。不过需要注意的是,的设计目的是在保证性能的同时也保留了多线程间的顺序风险,因此在使用中要谨慎考虑实际需求。

1182

华为2024机考真题集合

2.中位数是指把一组数据从小到大排列,最中间的那个数,如果这组数据的个数是奇数,那最中间那个就是中位数,如果这组数据的个数为偶数,那就把中间的两个数之和除以 2,所得的结果就是中位数。在实际考试中会随机抽三道题目进行考察,其中两道100分卷题和一道200分卷题,总共400分。幼儿园组织活动,老师布置了一个任务:每个小朋友去了解与自己同一个小区的小朋友还有几个。3.查找整型数组中元素的众数并组成一个新的数组,求新数组的中位数。1.众数是指一组数据中出现次数量多的那个数,众数可以是多个。

qq_25360769的博客 5093

[C++]内存管理:串顺序存储的内存回收

在串(字符串)的顺序存储中,内存回收的方式取决于字符串的存储方式以及所使用的编程语言和相关库。以下以 C++ 为例进行说明,因为 C++ 对内存管理有较为直接的控制。

qq_55948352的博客 81

Android13系统设置第三方app为设备所有者

DevicePolicyManagerService.java文件所在目录是frameworks/base,记得将包类名换成自己app的。2. 将拷贝到系统分区下的两个xml文件继续通过init.rc服务拷贝到data/system/目录下并修改权限。1. 拷贝设备所有者和设备管理器两个对应的xml文件到系统分区。

wzh048503的博客 2507

C++顺序表(SeqList)从零实现:工业级动态数组与内存管理实战

顺序表是数据结构中最基础的线性存储结构,其核心原理是使用一段连续的内存空间存储数据元素,类似于数组但提供了动态管理能力。从技术价值看,它封装了原始数组,实现了自动扩容、随机访问和高效尾部操作,是理解更复杂数据结构(如链表、栈、队列)的基石,也是学习C++面向对象编程和资源管理的经典案例。在工程实践中,顺序表通过模板实现泛型、采用深拷贝避免内存问题、利用移动语义提升性能,并需特别注意迭代器失效和异常安全等陷阱。本文以C++实现工业级顺序表为例,深入探讨了动态数组的内存管理机制,并针对深拷贝和移动语义等关键概念

weixin_32288959的博客 276

pe卸载win10更新补丁_禁用Win10系统更新

众所周知,最近WIN10的强制更新导致了很多问题。如:(部分专业软件系统更新后无法使用,硬件中键盘、鼠标、指纹之类的硬件,更新补丁后失灵等问题)。而之前的禁用WindowsUpdate的方法已经失效。下面我们另辟蹊径,利用系统权限的方法使Win10无法更新,达到禁用更新的目的。1.计算机管理-服务中禁用Windows Update服务。2.打开C:\Windows\softwareDistribu...

weixin_39702479的博客 1万+

5G MBS(组播广播服务)深度解析:从标准架构到商用实践

5G MBS(组播广播服务)深度解析:从标准架构到商用实践

大家好,我是码农老gou,一名专注于编程技术的程序员。在这个博客中,我将与大家分享我在工作和学习中的经验、见解以及一些实用的技术干货。 1082

数据结构与算法 顺序串的基本运算

1、顺序串与顺序表的顺序存储结构类似2、这实验的顺序串采用顺序串本身来表示,不是顺序串指针,所以由操作系统分配其存储空间,在超出作用域时释放其存储空间,所以摧毁顺序串运算不包含任何操作。

XLB 1086

ST MCSDK V6.2.0实战:手把手教你配置HSO-ST观测器,体验500ms收敛的电机无感控制

本文详细介绍了ST MCSDK V6.2.0中HSO-ST观测器的配置方法,实现500ms内电流收敛的无感电机控制。从环境搭建、核心参数解析到无感启动优化,提供实战技巧和性能调优策略,帮助开发者快速掌握高效电机控制技术。

weixin_28720767的博客 580

C++多继承下虚析构函数调用顺序详解与内存管理陷阱

在C++面向对象编程中,虚函数是实现运行时多态的核心机制,它通过虚函数表(vtable)动态绑定函数调用。虚析构函数则是确保多态对象资源安全释放的关键,其调用顺序遵循构造的逆序原则,由编译器生成的析构函数链在运行时通过虚表调度执行。这一机制对于避免内存泄漏和未定义行为具有重要技术价值,尤其在复杂继承体系如多继承和菱形虚拟继承中至关重要。在实际应用场景中,当通过基类指针删除派生类对象时,若基类析构函数非虚,将导致派生类部分资源无法释放,引发严重问题。本文聚焦于C++多继承下虚析构函数的调用顺序这一常被忽略的细

wjxbj的博客 734

遥感大模型核心技术之自监督预训练技术研究(2025年9月版)

本文系统探讨了自监督预训练技术在遥感大模型构建中的核心作用。随着遥感数据进入"大数据"时代,传统监督学习面临"数据丰富、标签贫乏"的瓶颈,而自监督预训练通过设计代理任务从无标签数据中学习特征表示,成为驱动大模型发展的关键引擎。文章分析了遥感领域对自监督预训练的三大独特挑战:多源异构性、地理空间约束和时空动态性,并提出了针对性解决方案:掩码自编码(MAE)技术及其演进、地理对比学习与多模态融合、位置感知预训练与多任务学习。

panshengwu的博客 842

Rancher入门到精通 - Nginx Ingress 配置指南

Rancher 是一个开源的容器管理平台,它提供了一套完整且易于使用的工具来部署、管理和扩展容器化应用。而 Nginx Ingress 则是一个常用的开源 Ingress 控制器,用于将外部请求流量导入到 Kubernetes 集群中的服务。在开始之前,请确保您已经正确安装了 Rancher 平台和 Nginx Ingress 控制器。

TechInk的博客 1311

C++顺序栈从零实现:数据结构、内存管理与性能优化全解析

栈作为一种基础的数据结构,遵循先进后出(LIFO)原则,在计算机科学中有着广泛的应用。其底层原理通常通过数组或链表实现,其中顺序栈利用连续内存空间提供高效的访问性能。从技术价值看,手动实现顺序栈能深入理解内存管理、边界控制和模板编程等核心概念,这是构建可靠软件系统的关键能力。在实际应用场景中,栈不仅用于函数调用、表达式求值等基础算法,更是理解STL容器适配器设计思想的基础。本文通过实现一个完整的顺序栈类,详细讲解了动态内存分配、深拷贝与移动语义等C++高级特性,并探讨了倍增扩容策略的性能优化。对于学习数据结

weixin_30444111的博客 307

arkts编译报错-arkts-limited-stdlib错误【Bug已完美解决-鸿蒙开发】

在使用 Deveco Studio 4.0 beta2 开发应用时,报错。ERROR: ArkTS:ERROR File: D:/prRevivw/3792/applications_appsamg优快云o”见台难忘Usage of standardlibrary is restricted (arkts-limited-stdlib)这是 ArkTS 新增的语法规则。

一键难忘的博客 3778

vector 的内存管理以及顺序容器的选择

1. vector 自增长 与容器大小相关的函数有以下几个: size()   //返回当前元素个数 max_size()    // 当前能存储元素的最多个数 capacity()     // 容器在重新获得更多存储空间时,可以存储的元素总个数 reserve()     //指定vector 预留多少个元素的存储空间 ,当reserve () vector容器为了获取

所有内容都已迁移到 https://huqijun.org 893

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

C++内存管理(可能对面试有用)

如果程序需要的内存大于物理内存,或者内存中剩余的空间不足以容纳当前程序,那么操作系统会将内存中暂时用不到的一部分数据写入到磁盘,等需要的时候再读取回来,而我们的程序只管使用 4GB 的内存,不用关心硬件资源够不够。这是一个很大的值,不但物理内存不可能达到这么大,CPU的寻址能力也没有这么大,实现64位长的虚拟地址只会增加系统的复杂度和地址转换的成本,带不来任何好处,所以 Windows 和 Linux 都对虚拟地址进行了限制,仅使用虚拟地址的低48位(6个字节),总的虚拟地址空间大小为。

白水空空 1303

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

【C++复习2】内存篇

怎么检测?第一:良好的编码习惯,使用了内存分配的函数,一旦使用完毕,要记得使用其相应的函数释放掉。第二:将分配的内存的指针以链表的形式自行管理,使用完毕之后从链表中删除,程序结束时可检查改 链表。第三:使用智能指针。第四:一些常见的工具插件,如ccmalloc、Dmalloc、Leaky、Valgrind等等。

2301_79582015的博客 709

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

C++ primer -顺序容器

6、若只在创建时需要在中间插入元素,且要求在使用时支持随机访问:可以先试试在vector尾部插入元素后,使用sort()将元素排序到中间位置,如果必须在中间插入,先使用list,再将list中的元素拷贝到vector中。reserve不改变原来的容器内容,即若n小于容器现有元素个数,容器大小不变,而当n大于容器现有元素个数时,容器大小依旧不变,只对所在的内存空间重新分配,扩容至至少为n。所有顺序容器都提供了快速顺序访问的能力,但在1、插入/删除元素的代价,2、非顺序访问元素的代价上有所牺牲。

qq_36369228的博客 801

【C++】构造函数、析构函数在局部、静态以及全局的调用和释放顺序

在局部域中,后定义的先析构,满足"后进先出",先定义的先构造 局部和静态:局部优先调用对应的析构函数,满足"后进先出",静态最后调用析构函数 局部,静态,全局:构造顺序是从 全局、静态--> 局部、静态,析构顺序是 局部 --> 全局、静态("后进先出")

cilmy23的note 2560

1.2 C++内存

栈更适合临时、局部变量,生命周期短,效率高,自动管理;堆更适合长生命周期的动态对象,灵活,但需要手动管理内存,有可能导致内存泄漏或碎片问题。malloc分配的内存位于堆中,手动管理。局部变量位于栈中,系统自动管理。在 C/C++ 中,malloc和局部变量的内存分配分别发生在不同的内存区域:malloc分配内存malloc(Memory Allocation)函数用于在**堆(Heap)**中分配内存。堆内存是动态分配的,程序员需要手动管理,即使用malloc分配后需要调用free。

LIHONGLI000125的博客 842

数据结构 动态分配实现顺序表 C/C++申请或释放内存(new + delete)

代码需求:使用动态分配的方式实现顺序表,并且添加可以增加顺序表空间的功能。 实现代码: #include <iostream> #include <stdlib.h> using namespace std; #define InitSize 64 // 默认的最大长度 typedef struct { int *data; // 动态分配数据的指针 int maxSize; // 顺序表的最大容量 int length; // 顺序表的当前长度.

clzZ2的博客 3264

【C++内存管理】浅析C++中函数调用时的内存分配-函数局部变量的内存分配

学习了C++这么久,内存问题感觉一直是一个拦路虎一样,说懂了感觉懵懵懂懂,说没懂吧,感觉也不是啥都不知道。有必要把它整理出来作为自己的一个知识体系的架构吧。 现在就从函数调用时的内存管理入手。 一、函数中普通变量的内存分配问题 我们知道当一个函数进行调用时,函数的形参、以及函数的局部变量都会在栈中被分配内存,而栈又可分为两种,一种是栈底不变,栈顶不断的动态变化;另一种是栈顶不变,栈底在动态变

Hyman的博客 2775
上一篇: c++-内存管理-内存对齐方式
下一篇: c++-内存管理-array allocator
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值