如果 class 有析构函数,堆数组分配 4 个额外字节

Heap array allocates 4 extra bytes if class has destructor

我是 C++ 的新手,最近一直在研究内存分配。我发现当你用析构函数声明一个 class 时,像这样:

class B
{
public:
    ~B() { }
};

然后像这样创建一个堆数组:

B* arr = new B[8];

分配器分配了 12 个字节,但是当我删除析构函数时,它只分配了 8 个字节。这就是我衡量分配的方式:

size_t allocated = 0;

void* operator new(size_t size)
{
    allocated += size;
    return malloc(size);
}
void deallocate(B* array, size_t size)
{
    delete[] array;
    allocated -= size * sizeof(B);
}

当然,我必须手动调用 deallocate,而 new 运算符会自动调用。

我在使用 std::string* 时发现了这个问题,我意识到释放器在 int* 上工作正常,但在前者上却不行。

有谁知道为什么会这样,更重要的是:如何在运行时以编程方式检测这些?

提前致谢。

您正在查看编译器如何处理 new[]delete[] 的实现细节,因此对于自答案以来分配的额外 space 没有明确的答案将特定于一个实现——尽管我可以在下面提供一个可能的原因。

由于这是实现定义的,您无法在运行时可靠地检测到它。更重要的是,不应该有任何真正的理由这样做。特别是如果您是 C++ 的新手,这个事实更像是一件需要了解的 interesting/esoteric 事情,但是在运行时检测它应该没有真正的好处。

同样重要的是要注意,这只发生在数组分配中,而不是对象分配中。例如,以下将打印预期数字:

struct A {
    ~A(){}
};
struct B {
};

auto operator new(std::size_t n) -> void* {
    std::cout << "Allocated: " << n << std::endl;
    return std::malloc(n);
}
auto operator delete(void* p, std::size_t n) -> void {
    std::free(p);
}

auto main() -> int {
    auto* a = new A{};
    delete a;

    auto* b = new B{};
    delete b;
}

输出:

Allocated: 1
Allocated: 1

Live Example

额外的存储空间只分配给具有非平凡析构函数的类型:

    auto* a = new A[10];
    delete[] a;

    auto* b = new B[10];
    delete[] b;

输出:

Allocated: 18
Allocated: 10

Live Example


为什么 发生这种情况的最可能原因是单个 size_t 的额外簿记被保留在包含非平凡析构函数的分配数组的开头。这样做是为了在调用 delete 时,语言可以知道有多少对象需要调用其析构函数。对于非平凡的析构函数,它能够依赖其释放函数的底层 delete 机制。

对于 GNU ABI,额外存储空间为 sizeof(size_t) 字节这一事实也支持这一假设。 x86_64 的构建产生 18 分配 A[10]8 字节用于 size_t)。 Building for x86 yields 14 用于相同的分配(4 字节用于 size_t)。


编辑

我不建议在实践中这样做,但实际上您可以 查看 来自数组的额外数据。来自 new[] 的分配指针在返回给调用者 (which you can test by printing the address from the new[] operator) 之前得到调整。

如果您将此数据读入 std::size_t,您可以看到此数据 — 至少对于 GNU ABI — 包含已分配对象数量的准确计数。

同样,我不建议在实践中这样做,因为这会利用实现定义的行为。但只是为了好玩:

    auto* a = new A[10];
    const auto* bytes = reinterpret_cast<const std::byte*>(a);
    std::size_t count;
    std::memcpy(&count, bytes - sizeof(std::size_t), sizeof(std::size_t));

    std::cout << "Count " << count << std::endl;

    delete[] a;

输出:

Count 10

Live Example