C++ 新操作员分配功能订单连续性&;初始值

C++ 新操作员分配功能订单连续性&;初始值,c++,new-operator,dynamic-memory-allocation,C++,New Operator,Dynamic Memory Allocation,C++标准第3.7.3.1节说 由分配的存储的顺序、连续性和初始值 未指定对分配函数的连续调用。” 秩序和连续性的含义是什么?为什么没有说明?为什么初始值也未指定?它本质上意味着新的操作符可以从系统中它认为必要的任何地方分配内存,并且不依赖于程序的任何分配顺序。顺序 意味着分配器不受限于返回稳定增加或减少的地址(或任何其他模式)。这是有意义的,因为在程序的生命周期中,内存通常会被多次回收和重用。可以向分配器请求以某种方式定义存储顺序,但在代码执行和内存效率方面,与相当大的开销相比,几乎没有什么好

C++标准第3.7.3.1节说

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


秩序和连续性的含义是什么?为什么没有说明?为什么初始值也未指定?

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

顺序
意味着分配器不受限于返回稳定增加或减少的地址(或任何其他模式)。这是有意义的,因为在程序的生命周期中,内存通常会被多次回收和重用。可以向分配器请求以某种方式定义存储顺序,但在代码执行和内存效率方面,与相当大的开销相比,几乎没有什么好处(如果有的话)

换句话说,如果B在A之后分配,C在B之后分配,那么A、B和C可能以任何顺序出现在内存中(ABC、BAC、CAB、CBA……)。您知道A、B和C的每个地址都是有效的,但仅此而已

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

连续性
这是一个类似的约束,基本上说,分配器在后续分配中返回的任何内容之间可能存在或不存在“漏洞”

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

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

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

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



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

这意味着如果用“代码>新< /CODE >分配内存一次,然后重新分配,就不能保证第二块内存位于第一个内存之后,也不一定指定了分配内存中的字节值。指定顺序,或者初始化内存到例如<代码> 0代码>代码,需要花费时间,而C++中,你不需要支付你不需要的东西。这意味着没有规范,即<代码>新< /COD>调用将返回一个“后”、“前”、“立即相邻”等地址,以调用先前的调用,也不返回其内容(填充为零、掩码填充等)。. 这些条件将对实现施加限制,而这些限制不需要以定义的方式使用该语言。因此,没有理由有这样的限制。“必要的”?更像是“Opportunity”。“同样,可能要求分配器以连续的方式返回对象,但……”这与标准中关于分配函数的规定相矛盾:“返回的指针应适当对齐,以便可以转换为指向任何适当完整对象类型的指针。”同样来自cppreference about
std::max_align_t
:“分配函数(如std::malloc)返回的指针对于任何对象都是适当对齐的,这意味着它们的对齐至少与std::max_align_t一样严格。”