除了分配内存和调用构造函数之外,C++的new运算符还有什么作用?

8
除了分配内存和调用构造函数之外,new 运算符还有哪些其他作用?
3个回答

18
C++标准对 <new> 头文件中常用的 new 运算符的单个对象形式有如下规定:

行为要求:

返回一个指向适当对齐存储器(3.7.3)的非空指针,否则引发 bad_alloc 异常。此要求对该函数的替换版本具有约束力。

默认行为:

— 执行循环:在循环内部,该函数首先尝试分配请求的存储器。尝试是否涉及调用标准 C 库函数 malloc 是未指定的。

— 如果尝试成功,则返回指向已分配存储器的指针。否则,如果 set_new_handler() 的最后一个参数是空指针,则抛出 bad_alloc 异常。

— 否则,该函数调用当前的 new_handler (18.4.2.2)。如果被调用的函数返回,则重复循环。

— 当分配请求的存储器尝试成功或被调用的 new_handler 函数不返回时,该循环终止。

C++标准还有很多其他关于 new 运算符和动态内存分配的内容(非常多),但我认为“默认行为”列表很好地概括了 new 运算符的基本知识。

1
有没有类似于新的对象大小存储方式,以便在使用指针删除对象时可以使用? - yesraaj
这肯定取决于具体的实现,而且在不同的平台上可能会有所不同。 - Anteru
这并没有被标准所规定。通常会使用malloc()或类似的函数,并且会使用相同的技术(如果使用malloc()则完全相同)来保持内存块的大小。 - Michael Burr
如果你正在执行一个数组的 new/delete 操作,那么所使用的技巧可能是在 C++ FAQ Lite 中讨论过的技巧之一:http://www.parashift.com/c++-faq-lite/freestore-mgmt.html#faq-16.14。 - Michael Burr
单个对象的new操作不需要存储分配块的大小,因为delete语句知道对象的大小,可以释放正确数量的空间。 - Motti
对于多态对象,析构函数必须是虚函数,编译器可以利用这一点来确定对象的实际大小。 - Motti

9
我已经在这个答案中解释了它的作用。它解释了以下内容:
  • new获得内存
  • new处理内存失败
  • new处理构造函数异常
  • new处理特殊放置和nothrow版本
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个字节用于存储在对象分配区域之前的计数。如果我们使用没有大小参数的dealloc函数(只需从operator delete中删除它),我们将得到以下输出:

sizeof f: 1
new    p: 0x9451008
new size: 1
&f_     : 0x9451008
delete p: 0x9451008

这里的C++运行时不再关心大小,因此不再存储它。请注意,这高度依赖于具体实现,这就是gcc在这里执行的操作,以便能够在成员operator delete中告诉您大小。其他实现可能仍然会存储大小,并且如果有类的析构函数需要调用,则很可能会存储大小。例如,只需在上面添加~f() { }就可以使gcc存储大小,而不管我们编写了哪个释放函数。


你能解释一下在实现内存池时使用 operator delete[] 和大小的语法吗?也就是说,你能帮我看看 https://dev59.com/CkzSa4cB1Zd3GeqPkCyc 吗?谢谢! - Tobias

0

这取决于它是否被重载,如果您为调试构建了应用程序,如果您正在使用内存泄漏检测器,如果您有某种内存池方案,如果您有像Boehm垃圾收集器之类的东西来标记/取消标记位等等。它可能在内部执行很多自定义操作,或者根本没有特殊操作。


我认为他已经通过引用“标准”的说法来涵盖了这一点。 - Martin York

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接