【问题标题】:Does delete (non array form) know the total amount of memory allocated by either new or new[]delete(非数组形式)是否知道 new 或 new[] 分配的内存总量
【发布时间】:2015-12-07 03:47:43
【问题描述】:

这个问题是作为 Does delete[] deallocate memory in one shot after invoking destructors? 的一部分提出的,但作为一个单独的问题移出。

似乎(如果错了请纠正我deletedelete[] 之间的唯一区别是delete[] 将获取数组大小信息并在所有这些上调用析构函数,而delete 将破坏唯一的第一个。特别是,delete 还可以访问有关new[] 分配的总内存量的信息。

如果不关心破坏动态分配的数组元素,只关心newnew[] 分配的内存被释放,delete 似乎能够做同样的工作。

这个How does delete[] "know" the size of the operand array? 问题的已接受答案有一条来自@AnT 的评论,我引用了

还请注意,数组元素计数器仅适用于具有非平凡析构函数的类型。对于具有平凡析构函数的类型,计数器不会被 new[] 存储,当然,也不会被 delete[] 检索

此评论表明,通常delete 表达式知道分配的整个内存的数量,因此知道最终一次性释放多少内存,即使内存包含一个数组的元素。所以如果有人写

auto pi = new int[10];
...
delete pi;

尽管标准认为这是 UB,但在大多数实现中,这不应该泄漏内存(尽管它不可​​移植),对吧?

【问题讨论】:

  • 为什么这转移到一个单独的问题?
  • 内存泄漏与内存碎片不同。您可能由于碎片而没有可用内存,但仍然没有内存泄漏。
  • 即使它能够释放内存,为什么你不想破坏对象?为什么标准委员会会足够关心这种行为来定义它?
  • @EdHeal 这是delete 特有的。正如@black 在链接问题中所建议的那样,我应该将单独个问题移出。
  • 本质上是同一个问题。您假设deletedelete[] 将“重新分配”内存,就好像两个运算符都知道要以相同方式重新分配的内存大小一样。它们有可能以完全不同的方式实现吗?还有为什么不修复代码。

标签: c++ memory-management


【解决方案1】:

避免所有构造引发未定义行为的具体原因是,编译器有权假设未定义行为永远不会发生。例如,给定这个程序...

#include <iostream>
#include <cstring>
int main(int argc, char **argv)
{
    if (argc > 0) {
        size_t *x = new size_t[argc];
        for (int i = 0; i < argc; i++)
            x[i] = std::strlen(argv[i]);
        std::cout << x[0] << '\n';
        delete x;
    }
    return 0;
}

...编译器可能会发出与...相同的机器代码

int main(void) { return 0; }

...因为argc &gt; 0 控制路径上的未定义行为意味着编译器可能会假定该路径从未被采用。

【讨论】:

    【解决方案2】:

    在 C++ 标准下,对分配有 new[] 的东西调用 delete 只是未定义的行为,就像对分配有 new 的东西调用 delete[] 一样。

    实际上,new[] 将通过类似malloc 的方式分配内存,new 也是如此。 delete 将销毁指向的对象,然后将内存发送到 free 之类的东西。 delete[] 将销毁数组中的所有对象,然后将内存发送到free 之类的东西。 new[] 可能会分配一些额外的内存,以传递给delete[],从而为delete[] 提供要销毁或不销毁的元素数量。

    如果使用实际的malloc/free,则某些实现将允许使用指向 malloc 块中任何位置的指针。其他人不会。需要将与从malloc 获得的完全相同的值传递给free 才能定义它。这里有一个问题,如果new[] 为数组大小/元素步幅分配了一些额外的空间并将其卡在 before 块之前,那么 delete 将传递指向第一个的指针元素,那么delete 将传递free 一个不同于new[]malloc 获得的指针。 (我认为有一种架构会发生这种情况。)

    像大多数未定义的行为一样,您不再依赖于审核您编写的代码,而是现在致力于审核生成的程序集,您与之交互的 C/C++ 标准库, 在你确定你想要做的行为是否正确之前。在实践中,这是一种无法实现的负担,因此您的代码最终会产生负值,即使您检查事情是否按照您实际检查时所期望的方式工作。您如何确保每次更改编译器版本、标准库版本、操作系统版本、系统库或编译器时都会进行相同的检查(对生成的二进制文件及其行为)?

    【讨论】:

    • 你能指出将指向块中间的指针传递给 free 的地方在 C 或 C++ 中是明确定义的吗?
    • “指向空闲块中任意位置的指针是 free 的有效参数”——不。
    • @n.m.呵呵,没错。固定的。我记得那个工作,但我不应该在没有检查的情况下声称它在标准下是可以的。我的错。
    • @n.m.引用 C 标准:“如果 ptr 是空指针,则不会发生任何操作。否则,如果参数与内存管理函数先前返回的指针不匹配,或者如果空间已通过调用 free 或 realloc 被释放,行为未定义。”
    • @JamesKanze 我在上述帖子的早期编辑中说了一些愚蠢的话,n.m.引用了我的话。
    【解决方案3】:

    这是正确的。 delete 和 delete[] 之间的区别在于后者知道数组中分配的项目数,并在它们上的每个对象上调用析构函数。为了 100% 正确,两者实际上都“知道”它 - 为数组分配的项目数等于分配的内存大小(两者都知道)除以对象的大小。

    有人可能会问,为什么我们需要 delete[] 和 delete 比 - 为什么不能 delete 执行相同的计算?答案是多态性。通过指向基类的指针进行删除时,分配的内存大小将不等于静态对象的大小。

    另一方面,delete[] 不考虑对象被多态的可能性,这就是为什么动态数组永远不应该被视为多态对象(即分配和存储为指向基类的指针)。

    关于内存泄漏,对于POD类型,delete在数组上使用时不会发生内存泄漏。

    【讨论】:

    • 当然,以上所有信息都是特定于实现的——但对于我所见过的任何实际实现都是正确的。
    • 奇怪。它与我所知道的任何实现都不对应。
    • 我对@9​​87654322@ 和多态性之间的交互感到很困惑。你是说delete不知道元素指针指向的动态类型是什么?
    • 我也是,我想it's not correct
    • @Rich,你误解了我的意思。当然,delete 不知道动态类型。这就是为什么必须在多态类型上将析构函数设为虚拟的全部原因!所有 delete 所做的就是调用析构函数,通过虚拟表调用虚拟析构函数,静态调用非虚拟析构函数。
    猜你喜欢
    • 2017-01-31
    • 1970-01-01
    • 2011-05-08
    • 2015-05-01
    • 2011-05-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-06
    相关资源
    最近更新 更多