【问题标题】:Difference between operator new() and operator new[]()?运算符 new() 和运算符 new[]() 之间的区别?
【发布时间】:2011-05-24 00:11:11
【问题描述】:

fncs: operator new 和 operator new[] 之间有什么区别(NOT new 和 new[] 运算符)?当然调用语法除外?我问是因为我可以使用 ::operator new(sizeof(T)*numberOfObject) 为我的 obj 分配 X 个字节,然后使用数组表示法访问它们,那么 ::operator new[] 有什么大不了的。它只是语法糖吗?

#include <new>
#include <iostream>
#include <malloc.h>

using namespace std;
struct X
{
  int data_;
  X(int v):data_(v){}
};
int _tmain(int argc, _TCHAR* argv[])
{
  unsigned no = 10;
  void* vp = ::operator new(sizeof(X) * no);
  cout << "Mem reserved: " << _msize(vp) << '\n';
  X* xp = static_cast<X*>(vp);
  for (unsigned i = 0; i < no; ++i)
  {
    new (xp + i) X(i);
  }
  for (unsigned i = 0; i < no; ++i)
  {
    cout << (xp[i]).data_ << '\n';
  }
  for (unsigned i = 0; i < no; ++i)
  {
    (xp + i)->~X();
  }
  ::operator delete(vp);
  return 0;
}

【问题讨论】:

  • 除了您的代码没有使用标准 C++ 编译器编译(请查看main 的签名)之外,它没有说明使用operator new[]。也许您发布了属于某个其他问题的代码?
  • 最好不要在有关内存分配函数的问题中发布包含内存泄漏的示例代码。 (除非你问是否有泄漏)
  • @Alf 是的,很抱歉。我用VS。应该这么说
  • 您用我认为非法使用 delete 的方式替换了内存泄漏。该标准说“delete-expression 操作符会破坏由 new-expression 创建的最派生对象或数组。” vp 确实指向由 new-expression 创建的对象,但它是一个放置 new,并且对 new 和 delete 的调用不匹配。具体来说,以这种方式调用delete vp; 不会撤消它之前的vp = ::operator new(sizeof(X) * no); 行。
  • @There:有不同的版本让实施者有机会在特定情况下提供适当的优化。

标签: c++ memory


【解决方案1】:

这些函数(operator new 等)通常不打算显式调用,而是由 new/new[] 表达式隐式使用(对称地,operator delete/operator delete[] 函数由 @ 隐式调用987654326@/delete[] 表达式)。对非数组类型使用new 语法的表达式将隐式调用operator new 函数,而使用new[] 的表达式将隐式调用operator new[]

这里的重要细节是new[] 表达式创建的数组通常稍后会被delete[] 表达式销毁。后者需要知道要销毁的对象的数量(如果对象具有非平凡的析构函数),即必须以某种方式将这些信息从new[] 表达式(当它已知时)传递到相应的delete[] 表达式(需要时)。在典型的实现中,此信息存储在由new[] 表达式分配的块中,这就是为什么在对operator new[] 的隐式调用中请求的内存大小通常大于元素和元素大小。额外的空间用于存储家庭信息(元素数量,即)。稍后delete[] 表达式将检索该家庭信息并使用它来调用正确数量的析构函数,然后通过调用operator delete[] 实际释放内存。

在您的示例中,您没有使用任何这些机制。在您的示例中,您显式调用内存分配函数,手动执行构造并完全忽略破坏步骤(这没关系,因为您的对象具有微不足道的析构函数),这意味着至少出于破坏目的,您不需要跟踪确切的数组中的元素数。在任何情况下,您都可以在 no 变量中手动跟踪该数字。

但是,一般情况下这是不可能的。在一般情况下,代码将使用new[] 表达式和delete[] 表达式,并且元素的数量必须以某种方式从new[]delete[],这意味着它必须存储在内部,这就是为什么需要用于数组的专用内存分配功能 - operator new[]。它不等同于以上述产品为大小的单纯operator new

【讨论】:

  • 我会重新措辞最后一段。即使 new/delete 不需要计数(因为它始终为 1),它们也可以使用与 new[]/delete[] 相同的技术来实现(从而消除 new 与 delete 和 new 匹配的要求[] 与 delete[]) 匹配。分离的原因(我相信)是为了允许优化版本的 new/delete
  • @Martin:对于全局分配器和释放器函数,分离的原因几乎可以肯定是出于调试目的,因此库可以捕获不匹配 newdelete 运算符的人。例如class X p = new X[10]; delete p; 可以在 ::operator new[] 的分配跟踪帮助下被捕获。优化更可能取决于块的大小,而不是内部是否存储了单个对象或数组。
  • @Martin:我认为这是鸡和蛋的问题:newnew[] 的优化版本(new[] 在这里是“基本”功能),或者new[]new 的扩展(用于数组)版本(new 在这种情况下是“基本”功能)。我总是更喜欢以后一种方式看待它,newnew[] 更基本。你似乎更喜欢前者。也许你真的是对的。
【解决方案2】:

在您的示例代码中,您使用放置 new 执行 operator new[] 自动执行的构造 - 不同之处在于 new[] 将仅执行默认构造,而您正在执行非默认放置构造.

以下内容或多或少等同于您的示例:

#include <iostream>

using namespace std;

struct X
{
    int data_;
    X(int v=0):data_(v){}
};

int main(int argc, char* argv[])
{
    unsigned no = 10;

    X* xp = new X[no];

    for (unsigned i = 0; i < no; ++i) {
        X tmp(i);
        xp[i] = tmp;
    }

    for (unsigned i = 0; i < no; ++i)
    {
        cout << (xp[i]).data_ << '\n';
    }

    delete[] xp;

    return 0;
}

本例的不同之处在于:

  • 我认为这里的示例更具可读性(演员表可能很难看,而放置 new 是一种非常先进的技术,不常使用,因此不常被理解)
  • 它会正确销毁分配的对象(在示例中并不重要,因为对象是 POD,但在一般情况下,您需要为数组中的每个对象调用 dtor)
  • 它必须默认构造对象数组,然后遍历它们以设置对象的实际值 - this 是本示例中的一个缺点,与您的不同

我认为总的来说,使用new[]/delete[] 比分配原始内存和使用放置new 来构造对象要好得多。它将簿记的复杂性推到了这些运算符中,而不是在您的代码中。但是,如果发现“默认构造/设置期望值”这对操作的成本太高,那么手动操作的复杂性可能是值得的。这应该是一种非常罕见的情况。

当然,任何关于new[]/delete[] 的讨论都需要提及使用new[]/'delete[]should probably be avoided in favor of usingstd::vector`。

【讨论】:

  • 经过cmets的讨论,我们了解到他的问题是关于void* ::operator new(std::size_t)函数,而不是new操作符。
  • 阅读完 cmets 后,我比以往任何时候都更加困惑 - cmets 似乎在讨论如何正确销毁使用放置 new 创建的对象。我应该删除这个答案吗?它似乎仍然与问题中的内容相关(除了问题已被修改以销毁 placed 对象),但你说我错过了重点。
  • 我读这个问题的方式,他在问void* ::operator new[](size_t)是否是::operator new(size_t)的语法糖,即他是否可以直接调用标量版本并去掉中间人。答案是否定的,因为::operator new[]() 返回的指针可以(没有UB)传递给::operator delete[](void*)::operator new() 没有。顺便说一句,我的意思是我回答后的前四个 cmets,而不是问题,也许这就是混乱。
  • @Ben 和 operator new 返回的 void* 和 operator new[] 返回的 void* 有什么区别?
  • @There: 你没看我的评论吗?将::operator new[]() 的返回值传递给::operator delete[](void*) 是正确的。将::operator new() 的返回值传递给::operator delete[](void*) 会调用未定义的行为。仅仅因为两个函数具有相同的签名并不意味着它们是相同的。显然sin(x)cos(x) 的功能不同,尽管它们具有相同的签名。
【解决方案3】:

函数void* operator new(size_t)void* operator new[](size_t) 所需的行为之间没有太大区别,只是它们与不同的释放函数配对。

运营商本身是非常不同的。运算符之间的差异之一是使用了哪个分配函数,但最终还有许多其他差异,包括调用了多少构造函数等。但是您的示例代码没有使用运算符(嗯,它使用的是placement new)。您可能希望更改您的问题标题以明确这一点。

来自[basic.stc.dynamic.deallocation]部分:

如果释放函数终止 通过抛出异常,行为 未定义。第一个值 提供给释放的参数 函数可能是一个空指针值; 如果是这样,如果释放 函数是在 标准库,调用没有 影响。否则,值 提供给operator delete(void*) in 标准库应是其中之一 前一个返回的值 调用任一运算符 new(std::size_t) 或运营商 new(std::size_t, const std::nothrow_- t&) 在标准库中,以及 在标准中提供给operator delete[](void*) 的值 图书馆应是价值观之一 由先前的调用返回 operator new[](std::size_t) 或 标准中的operator new[](std::size_t, const std::nothrow_t&) 图书馆。

【讨论】:

  • @Ben 你为什么说我的例子没有使用这些运算符? ::operator new(sizeof(X) * no) 怎么样?这不是使用其中之一吗?
  • @Ben 你也在说“运营商本身非常不同......”。您能否提供一些链接,以便我(和其他用户)可以阅读这些差异?谢谢
  • 不,这是调用名为operator new 的全局函数。此代码使用运算符 new:xp = new P(0);
  • @Stuart:对此我没有完美的答案。我可以想出很多理由来为来自不同池的不同类型对象(不同大小)分配内存,并且我可以看到任何定义自定义运算符 new 的类都可能不想从中分配数组(尤其是。考虑到允许数组存储析构函数数量的开销,删除需要稍后调用),但现在我想不出任何理由让全局单个对象和数组以不同方式分配。尽管与特定类别的案例保持一致是一件好事,但 IMO。
  • @Stuart:这是另一个:它提供了一种方法来检测许多不匹配的new/delete,这对于分配器的调试版本来说非常有用,因为它发现没有调用析构函数的真正问题。
【解决方案4】:

分配是一回事,对象构造/销毁是另一回事。

new 执行一次分配和一次构造。但是,new[] 仍然会分配一个连续的内存块,但会调用很多构造函数。

deletedelete[]的情况相同。

顺便说一句,我不能 100% 确定我要说的内容,但我相信如果您在从 new[] 收到的地址上调用 delete,您不会立即发生内存泄漏。整个内存块可能会被释放。但是,这是无效的,因为您只会在第一个对象上调用析构函数,而不是在数组中的每个对象上调用。这可能会导致次要内存泄漏......当然,由于构造函数和析构函数的 1-1 关系破裂,会导致大量逻辑错误。

(另外,请记住考虑使用boost::arraystd::vector 而不是new[]!:))

【讨论】:

  • 在cmets讨论后得知他的问题是关于void* ::operator new(std::size_t)函数,而不是new运算符。
【解决方案5】:

正如user sankozhis answerin fact the same question 中解释的那样,拥有单独的operator new[] 旨在分别在类中重载单个对象分配和数组分配。

例如,如果您有一些特定的类,并且您知道它的实例永远不会大于 50 字节,您可能希望为该类重载 operator new,以便在超快分配器上为大小块分配其实例50.

但是如果用户调用new[] 怎么办?该数组可以有任意数量的元素,因此您不能在自定义分配器上普遍分配它们。解决方案是您不必关心数组分配,除非您愿意。

【讨论】:

    猜你喜欢
    • 2013-06-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多