大小运算符delete []永远不会被调用

时间:2019-04-30 17:34:24

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

我试图跟踪我的开发中分配了多少内存。跟踪分配很容易,因为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++不会调用任何大小的取消分配,Intel编译器也不会

我的代码在任何方面都不正确吗?我误会了标准吗?还是g ++和clang ++都没有遵循标准?

2 个答案:

答案 0 :(得分:3)

根据cppreference.com(通常是可靠的),未指定哪个版本称为“当删除不完整类型的对象以及非类和易碎类类型的数组 ”(我的重点)。

似乎默认情况下,编译器也会禁用大小删除。

答案 1 :(得分:0)

大小释放 API 的目的不是帮助您跟踪已分配或释放的内存量,并且无论如何都不能可靠地使用它。 sized deallocation API 的目的是提高支持 sized deallocations 的内存分配器的效率,因为它让编译器在某些情况下调用 sized-deallocation 方法,这意味着内存分配器不需要查找大小进行释放时释放的指针。 Andrei Alexandrescu 在他 2015 年 CppCon 关于 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);
}

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

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