c++运算符new[]/delete[](不是我的)是否调用运算符new/delete?
在我用我自己的实现替换operator new
和operator 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
由于分配运算符new
和new[]
几乎做同样的事情(a),因此根据另一个来定义一个是有道理的。它们都用于分配给定大小的块,无论您打算将其用于什么目的。delete
和delete[]
也是如此。
事实上,这是标准所要求的。C++1118.6.1.2 /4
(例如)声明operator new[]
的默认行为是它返回operator new(size)
。/13
对operator 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); }
当您更换new
和delete
功能时,new[]
和delete[]
功能仍将在幕后使用它们。但是,将new[]
和delete[]
替换为您自己的函数,这些函数不会调用您的new
和delete
会导致它们断开连接。
这就是为什么你会看到问题第一部分描述的行为。
根据你问题的第二部分,你看到了我期望看到的。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 MyClass
和new MyClass[7]
之间的差异晚于建造对象的分配阶段。基本上,它们都分配一次所需的内存,然后根据需要在该内存中构造任意数量的对象(前者一次,后者七次)。
(b)并且允许实现不内联存储任何控制信息。我记得在嵌入式系统上工作时,我们知道没有分配会超过 1K。所以我们基本上创建了一个没有内联控制块的竞技场。相反,它有一点内存块,几百个这样的1K块,并使用位图来决定哪个正在使用,哪个是空闲的。
万一有人要求超过1K,得到了NULL
.那些要求小于或等于1K的人无论如何都会得到1K。不用说,它比实现中提供的通用分配函数要快得多。