【问题标题】:The sized operator delete[] is never called大小运算符 delete[] 永远不会被调用
【发布时间】:2019-09-19 10:04:01
【问题描述】:

我正在尝试跟踪在我的开发过程中分配了多少内存。跟踪分配很容易,因为void *operator new (size_t)void *operator new[](size_t) 的过载允许跟踪分配了多少。 使用 C++ one can resort to a technique of over-allocating memory to store the size of the allocation

从 C++14 开始,有相应的 void operator delete(void*p, size_t size)void operator delete[](void*p, size_t size) 应该允许准确地说明每次取消分配(除了不完整类型的删除,然后留给实现)。

但是,尽管第一个版本由 g++ 调用,其中调用了删除单个对象,但我还没有找到调用第二个的单个编译器。这是我的测试代码:

#include <iostream>
size_t currentAlloc;

void * operator new(size_t size)
{
    currentAlloc += size;
    std::cout << "1\n";
    return malloc(size);
}

void *operator new[](size_t size)
{
    std::cout << "3\n";
    currentAlloc += size;
    return malloc(size);
}

void operator delete(void *p) noexcept
{
    std::cout << "Unsized delete\n";
    free(p);
}

void operator delete(void*p, size_t size) noexcept
{
    std::cout << "Sized delete " << size << '\n';
    currentAlloc -= size;
    free(p);
}

void operator delete[](void *p) noexcept
{
    std::cout << "Unsized array delete\n";
    free(p);
}

void operator delete[](void*p, std::size_t size) noexcept
{
    std::cout << "Sized array delete " << size << '\n';
    currentAlloc -= size;
    free(p);
}

int main() {
    int *n1 = new int();
    delete n1;

    int *n2 = new int[10];
    delete[] n2;

    std::cout << "Still allocated: " << currentAlloc << std::endl;
}

使用g++ -std=c++14 test.Cclang++ -std=c++14 test.C 编译。 g++ 的输出结果:

1
大小删除 4
3
未调整大小的数组删除
仍分配:40

我期望为第二个delete 调用大小数组删除,最后打印的值为 0 而不是 40。clang++ 不会调用任何大小的取消分配,英特尔编译器也不会。

我的代码有什么不正确的吗?我误解了标准吗?还是g++和clang++都不符合标准?

【问题讨论】:

  • 您似乎假设void operator delete[](void *p) noexcept 中的free(p); 不会释放整个数组。
  • 在 c++ 中使用 malloc()free() 优于 new()delete() 的任何原因?
  • @FrançoisAndrieux 我不知道你是怎么想的?我没有对免费做出任何假设。我只是想跟踪我的代码在任何时间点当前分配了多少内存。
  • @Michel 您只在几个运营商中跟踪它。例如operator delete(void*) 可能会释放内存,但您没有在那里跟踪它。
  • /Zc:sizedDealloc 在这里没有任何区别,来自 MSDN 的关键引用是“当编译器无法确定被释放对象的大小时调用单参数版本”,编译器无法从原始指针轻松确定数组的大小。

标签: c++ c++14 c++17


【解决方案1】:

大小释放 API 的目的不是帮助您跟踪已分配或释放了多少内存,而且无论如何它都不能可靠地用于此目的。大小释放 API 的目的是提高支持大小释放的内存分配器的效率,因为它允许编译器在某些情况下调用 sized-deallocation 方法,这意味着内存分配器不需要查找执行释放时释放的指针。 Andrei Alexandrescu 在他的 2015 CppCon talk about the std::allocator API 中谈到了这一点。

大多数内存分配器都提供类似mallinfo(3) 的API,让您可以显式查询分配器以获取有关已分配或释放多少内存的统计信息;我建议您阅读有关您使用的任何分配器的文档,以了解如何访问这些统计信息。

您不能使用它来跟踪所有解除分配的总大小的原因是,通常编译器并不总是知道开始删除的对象的大小。例如下面的代码:

char *foo(size_t n) {
    char *p = new char[n];
    // do stuff with p, maybe fill it in
    return p;
}

void bar(char *p) {
    delete[] p;
}

void quux(size_t nbytes) {
   char *p = foo(nbytes);
   bar(p);
}

在这种情况下,内存在一个地方分配,但在其他地方释放,有关分配大小的信息在释放位置丢失。这个具体的例子非常简单,所以如果两个函数位于附近,优化编译器可能会看穿这个例子,但一般情况下,不保证将使用大小释放函数,它是编译器可能会这样做。

此外,即使在使用 -std=c++14 或(更高的标准版本,如 c++17 或 c++20)编译时,Clang 目前(截至 2020 年底)也不会启用大小释放;目前要让它使用大小的释放,您需要将 -fsized-deallocation 添加到您的 clang 命令行。

【讨论】:

    【解决方案2】:

    根据cppreference.com,这通常是可靠的,它没有指定哪个版本被称为“当删除不完整类型的对象和非类和易破坏类类型的数组”(我的重点)。

    似乎编译器默认禁用大小删除。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-10-27
      • 2013-10-12
      • 2012-03-27
      • 2013-08-29
      • 2013-11-03
      • 1970-01-01
      • 1970-01-01
      • 2015-05-06
      相关资源
      最近更新 更多