阅读:

接受的答案建议采取new T[n]如下措施(忽略对齐和 n = 0 的情况):

  • 如果n= 1 或 T 是可平凡破坏的:

    • 分配sizeof(T)字节
    • T在分配的内存上调用 ctor (如 placement-new)
  • 如果n> 1:

    • 分配sizeof(size_t + n * sizeof(T))字节
    • 将第一个 size_t 字节设置为值n
    • T在每个以下 n 个字节序列上调用 ctor sizeof(T)(类似于在循环中放置 new)

我可以依赖这个吗?也就是说,如果我new T[n]按照上面的方法在自己的代码中模拟,然后将结果传递给delete[]– 这会是明确定义且安全的吗?

相关问题:

13

  • 2
    new T[1]并且new T[N]会做同样的事情(存储元素的数量[通常就在数组元素之前]),因为delete[]在两种情况下都需要做相同的工作。


    – 


  • 2
    标准只是指定了必须发生什么,而不是如何实现。根据“as-if”规则,运行时可能会要求您将值(元素数量)写在纸上,然后稍后提供。您需要检查您的实现,但是大多数只是将元素数量存储在返回的数组地址之前的字节中,


    – 


  • 1
    new T[N]分配主要sizeof(T) * N + unspecified_y用于unspecifed_y存储N释放(对于琐碎的可能被省略T。因此“模拟”需要准确地知道您当前的编译器在做什么(这可能会在版本之间发生变化)。


    – 


  • 3
    “这会定义明确且安全吗?”我会说不。如果你想要模拟,分配/释放内存块,并进行新的放置并手动调用~T(大致就是这样std::vector


    – 


  • 3
    这是个东西,因为它是一个实现细节。我的编译器有三个内存区域:一个用于malloc/ free,一个用于new/ delete,一个用于new[]/ delete[]。它是一个堆管理器,专注于安全,而不是性能,并且可以检测一些常见问题,例如某些内存写入超限或不足,或者在一个区域外分配并尝试在另一个区域释放。


    – 


最佳答案
2

我可以依赖这个吗?也就是说,如果我按照上面的方法在自己的代码中模拟新的 T[n],然后将结果传递给 delete[] – 这样是否定义明确且安全?

不,这一切都未指定。您唯一可以确定的是,new T[n]将使用operator new[]至少与一样大的大小参数调用n*sizeof(T),并将返回一个指针,该T[n]指针有足够的空间容纳对象,并且已适当对齐。(在T情况下或者有额外的对齐保证。)其他一切都是未指定的实现细节。Tunsigned charcharstd::byte

如果您愿意依赖特定的 ABI 保证,那么您可能会取得更大的成功,尽管从技术上讲,按照标准仍然是 UB。例如,在 Itanium ABI 中,请参见,了解何时以及如何使用数组 cookie。该过程并不像您建议的那样。特别是,如果没有非平凡的析构函数,并且通常的数组释放函数不接受两个参数,则没有 cookie T。此外,还应用了一个纠正对齐的过程,并且没有特殊的长度情况1

3

  • 1. 调整了关于可轻易破坏类型的问题。2. 我确实说过“忽略对齐”。不过,我会阅读 Itanium ABI 的相关内容。我可以使用一些预处理器定义来检查 Itanium ABI 吗?


    – 


  • @einpoklum 我认为没有任何预处理器定义。这是构建系统通常必须知道的事情,因为您必须知道 ABI 才能与其他库链接。


    – 

  • 1
    @einpoklum 注意,可轻易破坏并不是完全条件。在某些情况下(当通常的数组释放函数需要两个参数时),无论如何都必须存储大小。


    – 

作为一般原则,避免对实现的工作方式做出假设。如果您绝对需要这样做,而您又没有实现的详细规范,那么您就是在冒险。即使您有规范,您也会使您的代码不可移植(这在以后可能会很重要,而且总是不受欢迎的),并且仍然有以后实施变更的风险。


在 hand 的情况下,永远不要传递给delete []您未从 new []’ 收到的内容,而是将其传递给delete [] exact once

几乎肯定行不通的是分配一个大数组(比如说)并将其部分传递给delete [] 。可能行得通的方法是使用malloc()或低级 O/S 调用(在标准库之外)进行分配并将其传递给delete [](但不保证也不推荐)

您可能想要实现一个,或者需要分配原始char内存(使用new []malloc()或其他方式),并使用放置new来在其中构造对象,并在您想要处置(或回收)它时直接调用析构函数,最终根据您获取它的方式释放该“原始”内存。您甚至可以玩游戏在堆栈上分配内存并允许return恢复它。

过早优化是万恶之源,像这样调整内存管理并不是首要任务。但如果您的算法正在分配和释放大量对象,那么它可能很有用。

需要特别注意的是,通过充分利用交换和移动语义,可以避免内存流失(大量newdelete)。它们和复制省略几乎是为了删除多余的“新建”/复制/“删除”序列而发明的,并且可以带来比优化分配更好的巨大改进。

您要做的事情可能合理。但是您尝试做的方式并不推荐,并且有更安全、更便携的方式来做这件事。

脚注:按照 OP 的要求,我在此答案中忽略了零长度数组和对齐,但是如果您去那里,它们都与自定义分配逻辑相关。