新的运算符分配函数顺序连续性和初始值

new operator allocation function order contiguity & initial values

本文关键字:连续性 顺序 运算符 分配 函数      更新时间:2023-10-16

C++标准第3.7.3.1节规定

"由分配的存储顺序、连续性和初始值对分配函数的连续调用是未指定的。">

秩序的含义是什么;邻接?为什么没有具体说明?为什么初始值也未指定?

订单
意味着分配器不受返回不断增加或减少的地址(或任何其他模式(的限制。这是有道理的,因为在程序的生命周期中,内存通常会被多次回收和重用。可以从分配器那里要求以某种方式定义存储顺序,但在代码执行和内存效率方面,与相当大的开销相比,收益非常小(如果有的话(。

换言之,如果B被分配在A之后,而C被分配在B之后,则A、B和C可以以任何顺序(ABC、BAC、CAB、CBA…(出现在存储器中。您知道A、B和C的每个地址都是有效的,但仅此而已。

正如@Deduplitor所指出的,并发编程中存在一个众所周知的问题,称为"ABA问题"。这间接地是因为新分配的对象原则上可以具有任何地址(请注意,这有点先进(
当您使用比较交换指令以原子方式修改内存位置时,就会出现ABA问题,例如在无锁定列表或队列中。您的假设是,当其他人同时修改了您试图修改的指针时,比较交换会检测到。然而,事实是它只检测地址的位模式是否不同。现在,您可能会释放一个对象并将另一个对象(向分配器请求存储(推送到容器,而分配器会给您完全相同的地址——这是绝对合法的。另一个线程使用compare交换指令,它通过得很好。砰
为了防止这种情况,无锁结构通常使用带有内置参考号的指针。

邻接性
这是一个类似的约束,基本上说,分配器在随后的分配中返回的任何东西之间可能存在也可能不存在"漏洞"。

这意味着,如果分配一个大小为8的对象,然后再分配另一个对象,分配器可能会返回地址A和地址A+8。但它也可能返回地址A+10,而不是根据自己的判断为第二次分配
这种情况几乎在每次分配中都会发生,因为分配器通常除了存储实际对象之外还存储元数据,并且通常按照特定大小(通常为16字节(的"存储桶"来组织内存
因此,如果你分配了一个整数(通常是4个字节(或一个指针(通常是8个字节(,那么在这个和你分配的下一个之间就会有一个"洞"。

同样,可能要求分配器以连续的方式返回对象,但与相对便宜的方式相比,这意味着严重的性能影响。

初始值
这意味着您必须正确初始化对象,并且不能假设它们具有任何特定的值。否,内存将不会自动初始化为零[1]

要求分配器为零初始化内存是可能的,但效率会比可能的低(尽管不像其他两个约束那样具有破坏性(。


[1]在某些情况下,确实如此。操作系统通常会在第一次向进程提供新页面之前将所有页面清零。这样做是为了安全,因此不会泄露机密/机密数据。然而,这是一个超出C++标准范围的实现细节。

这本质上意味着new运算符可以从系统中任何它认为必要的地方分配内存,并且不依赖于程序的任何分配顺序。