c++ 运算符 new[]/delete [] 是否调用运算符 new/delete?



c++运算符new[]/delete[](不是我的)是否调用运算符new/delete?

在我用我自己的实现替换operator newoperator delete后,下面的代码将调用它们:

int *array = new int[3];
delete[] array;

而当我替换了operator new[]operator delete[],那么上面的代码只会调用它们。

我的运算符实现:

void *operator new(std::size_t blockSize) {
std::cout << "allocate bytes: " << blockSize << std::endl;
return malloc(blockSize);
}
void *operator new[](std::size_t blockSize) {
std::cout << "[] allocate: " << blockSize << std::endl;
return malloc(blockSize);
}
void operator delete(void *block) throw() {
int *blockSize = static_cast<int *>(block);
blockSize = blockSize - sizeof(int);
std::cout << "deallocate bytes: " << *blockSize << std::endl;
free(block);
}
void operator delete[](void *block) throw() {
int *blockSize = static_cast<int *>(block);
blockSize = blockSize - sizeof(int);
std::cout << "[] deallocate bytes: " << *blockSize << std::endl;
free(block);
}

我还有第二个问题,可能不是那么相关,为什么代码打印:

[] allocate: 12
[] deallocate bytes: 0

取而代之的是:

[] allocate: 16
[] deallocate bytes: 16

由于分配运算符newnew[]几乎做同样的事情(a),因此根据另一个来定义一个是有道理的。它们都用于分配给定大小的块,无论您打算将其用于什么目的。deletedelete[]也是如此。

事实上,这是标准所要求的。C++1118.6.1.2 /4(例如)声明operator new[]的默认行为是它返回operator new(size)/13operator delete[]也有类似的限制。

因此,示例默认实现如下所示:

void *operator new(std::size_t sz) { return malloc(sz); }
void operator delete(void *mem) throw() { free(mem); }
void *operator new[](std::size_t sz) { return operator new(sz); }
void operator delete[](void *mem) throw() { return operator delete(mem); }

当您更换newdelete功能时,new[]delete[]功能仍将在幕后使用它们。但是,将new[]delete[]替换为您自己的函数,这些函数不会调用您的newdelete会导致它们断开连接。

这就是为什么你会看到问题第一部分描述的行为。


根据你问题的第二部分,你看到了我期望看到的。int[3]的分配要求三个整数,每个整数的大小为四个字节(在您的环境中)。这显然是12字节。

为什么它似乎释放零字节有点复杂。您似乎认为紧接您获得的地址之前的四个字节是块的大小,但事实并非如此。

实现可以自由地在内存领域(b)中存储他们喜欢的任何控制信息,包括以下可能性(这绝不是详尽无遗的):

  • 当前内存分配的大小;
  • 指向下一个(可能是上一个)控制块的链接;
  • 用于捕获竞技场损坏的 Sentinel 值(如0xa55a或控制块的校验和)。

除非您知道并控制内存分配函数如何使用其控制块,否则您不应该做出假设。首先,为了确保正确对齐,可以填充控制块,否则无用的数据。如果要保存/使用请求的大小,则需要使用以下方法自行完成:

#include <iostream>
#include <memory>
// Need to check this is enough to maintain alignment.
namespace { const int buffSz = 16; }
// New will allocate more than needed, store size, return adjusted address.
void *operator new(std::size_t blockSize) {
std::cout << "Allocating size " << blockSize << 'n';
auto mem = static_cast<std::size_t*>(std::malloc(blockSize + buffSz));
*mem = blockSize;
return reinterpret_cast<char*>(mem) + buffSz;
}
// Delete will unadjust address, use that stored size and free.
void operator delete(void *block) throw() {
auto mem = reinterpret_cast<std::size_t*>(static_cast<char*>(block) - buffSz);
std::cout << "Deallocating size " << *mem << 'n';
std::free(mem);
}
// Leave new[] and delete[] alone, they'll use our functions above.
// Test harness.
int main() {
int *x = new int;
*x = 7;
int *y = new int[3];
y[0] = y[1] = y[2] = 42;
std::cout << *x << ' ' << y[1] << 'n';
delete[] y;
delete x;
}

运行该代码会导致打印成功的值:

Allocating size 4
Allocating size 12
7 42
Deallocating size 12
Deallocating size 4

(a)new MyClassnew MyClass[7]之间的差异晚于建造对象的分配阶段。基本上,它们都分配一次所需的内存,然后根据需要在该内存中构造任意数量的对象(前者一次,后者七次)。


(b)并且允许实现不内联存储任何控制信息。我记得在嵌入式系统上工作时,我们知道没有分配会超过 1K。所以我们基本上创建了一个没有内联控制块的竞技场。相反,它有一点内存块,几百个这样的1K块,并使用位图来决定哪个正在使用,哪个是空闲的。

万一有人要求超过1K,得到了NULL.那些要求小于或等于1K的人无论如何都会得到1K。不用说,它比实现中提供的通用分配函数要快得多。

最新更新