new
除了分配内存和调用构造函数之外,操作员还做了哪些其他事情?
3 回答
C++ 标准对来自标头的 new 运算符的单个对象形式(通常使用的形式)有这样的说法<new>
:
要求的行为:
返回指向适当对齐存储的非空指针 (3.7.3),否则抛出 bad_alloc 异常。此要求对该功能的替换版本具有约束力。
默认行为:
— 执行循环:在循环内,函数首先尝试分配请求的存储空间。未指定尝试是否涉及对标准 C 库函数 malloc 的调用。
— 如果尝试成功,则返回指向已分配存储的指针。否则,如果 set_new_handler() 的最后一个参数是空指针,则抛出 bad_alloc。
— 否则,该函数调用当前的 new_handler (18.4.2.2)。如果被调用的函数返回,则循环重复。
— 当尝试分配请求的存储成功或调用的 new_handler 函数没有返回时,循环终止。
该标准还有很多关于新运算符和动态内存分配的其他内容(要说的很多),但我认为“默认行为”列表很好地总结了新运算符的基础知识。
我已经解释了它在这个答案中的作用。它解释了如何
new
获取内存new
处理内存故障new
处理构造函数异常new
处理特殊放置和非抛出版本
Michael 解释了默认分配器函数 (::operator new) 如何很好地获取内存以及它如何处理故障。我已经在他的评论中看到了您关于对象大小存储在哪里的问题。答案是,如果没有必要,就没有存储大小。请记住,C 不需要大小free
(并且 ::operator new 可以使用malloc
):
void * memory = malloc(x);
free (memory); // no need to tell it the size
这是一个示例,您可以在其中看到存储大小如何影响新表达式的数组形式的分配大小(我的其他答案未涵盖):
#include <cstddef>
#include <iostream>
struct f {
// requests allocation of t bytes
void * operator new[](std::size_t t) throw() {
void *p = ::operator new[](t);
std::cout << "new p: " << p << std::endl;
std::cout << "new size: " << t << std::endl;
return p;
}
// requests deleting of t bytes starting at p
void operator delete[](void *p, std::size_t t) throw() {
std::cout << "delete p: " << p << std::endl;
std::cout << "size : " << t << std::endl;
return ::operator delete[](p);
}
};
int main() {
std::cout << "sizeof f: " << sizeof (f) << std::endl;
f * f_ = new f[1];
std::cout << "&f_ : " << f_ << std::endl;
delete[] f_;
}
它将打印出如下内容:
sizeof f: 1
new p: 0x93fe008
new size: 5
&f_ : 0x93fe00c
delete p: 0x93fe008
size : 5
一个字节用于对象本身,4 个字节用于存储在对象分配区域之前的计数。现在,如果我们使用不带大小参数的释放函数(只是从运算符 delete 中删除它),我们会得到以下输出:
sizeof f: 1
new p: 0x9451008
new size: 1
&f_ : 0x9451008
delete p: 0x9451008
这里的 C++ 运行时不关心大小,因此不再存储它。请注意,这是高度特定于实现的,这就是 gcc 在这里所做的,以便能够告诉您成员运算符删除的大小。其他实现可能仍会存储大小,并且很可能会在有要为类调用的析构函数时存储。例如,只要在~f() { }
上面添加,gcc 就会存储大小,无论我们编写什么释放函数。
取决于它是否超载,是否构建用于调试的应用程序,是否使用内存泄漏检测器,是否有某种内存池方案,是否有类似 Boehm 垃圾收集器的标记/取消标记位,等等,等等。它可能在里面做了很多自定义的东西,或者根本没有什么特别的。