如果 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
额外的存储空间只分配给具有非平凡析构函数的类型:
auto* a = new A[10];
delete[] a;
auto* b = new B[10];
delete[] b;
输出:
Allocated: 18
Allocated: 10
为什么 发生这种情况的最可能原因是单个 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
我是 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
额外的存储空间只分配给具有非平凡析构函数的类型:
auto* a = new A[10];
delete[] a;
auto* b = new B[10];
delete[] b;
输出:
Allocated: 18
Allocated: 10
为什么 发生这种情况的最可能原因是单个 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