【问题标题】:Why is ::operator new[] necessary when ::operator new is enough?当 ::operator new 足够时,为什么需要 ::operator new[]?
【发布时间】:2014-07-07 04:39:37
【问题描述】:

众所周知,C++ 标准定义了两种形式的全局分配函数:

void* operator new(size_t);
void* operator new[](size_t);

此外,C++ 标准草案 (18.6.1.2 n3797) 说:

227) 这不是操作员的直接责任 new 或 operator delete 以注意重复 数组的计数或元素大小。执行这些操作 数组中的其他地方 new 和 delete 表达式。数组新 但是,表达式可能会增加运算符的大小参数 新获得空间来存储补充信息。

让我困惑的是:

如果我们从标准中删除void* operator new[](size_t);,而只使用void* operator new(size_t) 会怎样?定义冗余全局分配函数的基本原理是什么?

【问题讨论】:

  • 有趣的问题。通常,当向纯 C 系统添加 C++ 支持时,operator newoperator new[] 都只是转身并调用 malloc
  • 这里只是推测。但在为类类型重载这些运算符的情况下,这实际上是一个有效的区别,因此使全局版本具有相同的接口也许是一致的。
  • 这个注释很奇怪,因为没有理由说明非数组版本也可能不是increase the size argument to operator new to obtain space to store supplemental information,尽管没有对此发表评论。
  • @Keith:我记得在标准中的某处读到这是明确禁止的 - 发现它 - “它可能大于仅当对象是数组时创建的对象的大小”
  • @Keith:你说“大概没有理由为什么非数组版本也不会增加 operator new 的 size 参数以获得存储补充信息的空间” - 显然你在谈论 @987654328 @ expression 因为这就是标准所说的允许增加的地方 - 我只是说非数组等价物 not 允许这样做。如果你想推测它可能发生在其他地方,那很好,但很明显。

标签: c++ memory-management overloading standards new-operator


【解决方案1】:

我认为::operator new[] 可能对相当专业的系统很有用,其中“大而少”的数组可能由与“小而多”对象不同的分配器分配。然而,它目前是一个遗物。

operator new 可以合理地预期对象将在返回的确切地址处构造,但operator new[] 不能。分配块的第一个字节可能用于大小“cookie”,数组可能被稀疏初始化等。这种区别对于成员 operator new 变得更有意义,它可能专门针对其特​​定类。

无论如何,::operator new[] 并不是很重要,因为std::vector(通过std::allocator)是目前最流行的获取动态数组的方法,忽略了它。

在现代 C++ 中,自定义分配器通常是比自定义 operator new 更好的选择。实际上,应该完全避免使用new 表达式,以支持容器(或智能指针等)类,它们提供更多的异常安全性。

【讨论】:

  • 我很震惊,我说,震惊和沮丧std::vector 不使用new[]!嗯。
  • @einpoklum:vector 需要放置消息才能工作,而使用数组放置 new 是一场噩梦,尝试在像 vector 一样的预分配缓冲区中多次使用 new[] 是完全不可能移植的。
  • @david.pfx 哪一部分不正确?您含糊地提到了“专门的数组索引”和“CDC、IBM 等”,但这与具体的主张相去甚远,我的第一句话就涵盖了这一点。 (我没有提到内存池,我说的是“由不同的分配器分配”,这涵盖了架构特性的适应。)应该详细说明您的答案中的“用于数组索引的额外存储空间”;要求可能比您想象的要严格。
  • @einpoklum 如果需要,您可以创建自己的分配器,它与std::allocator 相同,除了调用::operator new[] 而不是::operator new(以及相应的delete)。但是,可能没有任何收获,因为每个实现都已经针对使用非数组函数分配数组的默认 std::vector 进行了优化。
【解决方案2】:

::operator new[] 和 ~delete[] 便于内存使用调试,是审计分配和释放操作的中心点;然后,您可以确保数组形式用于两者或都不用于。

如果非常不寻常/粗略的调整使用也有很多合理的:

  • 从单独的池中分配数组,可能是因为这显着提高了小型单对象动态分配对象的平均缓存命中率,

  • 数组/非数组数据的不同内存访问提示(ala madvise

这一切有点奇怪,超出了 99.999% 程序员的日常关注,但为什么要阻止它成为可能呢?

【讨论】:

  • @MooingDuck:仔细注意前导“~”... O_o.
  • 我认为这是一个错字,除了伪析构函数名称之外,我从未遇到过这种语法。
  • @MooingDuck:这是一般英语用法的简写——不是 C++ 表示法——表示某个先前条目的前缀出现在同一个地方,经常用于字典条目中;例如:trouble ~some ...
  • ... 英文还是 Perl? ……按照字典,我受到#define opərator operator的启发……
  • @Potatoswatter:把自己打晕……随便拍你的土豆。
【解决方案3】:

标准 (n3936) 清楚地表明这两个运算符用于不同但相关的目的。

operator new 调用函数void* operator new(std::size_t)。第一个参数必须与运算符的参数相同。它返回一个适当对齐的存储块,并且可能比所需的要大。

operator new[] 调用函数void* operator new[](std::size_t)。第一个参数可能大于提供给操作符的参数,以便在数组索引需要时提供额外的存储空间。两者的默认实现都是简单地调用 malloc()。

operator new[] 的目的是支持专门的数组索引(如果可用)。它与内存池或其他任何东西无关。在使用此功能的一致实现中,实现将在额外空间中设置专用表,并且编译器将为指令生成代码或调用使用这些表的库库支持例程。使用数组且未能使用 new[] 的 C++ 代码在这些平台上会失败。

我个人不知道有任何此类实现,但它类似于支持某些大型机(CDC、IBM 等)所需的功能,这些大型机的架构与我们熟悉和喜爱的英特尔或 RISC 芯片完全不同。

在我看来,接受的答案是不正确的。


为了完整起见,标准(主要在 S5.3.4 中的 n3936)包含以下内容。

  1. 分配“数组对象”或“非数组对象”的区别
  2. 对“数组分配开销”的引用,暗示可能需要额外的存储空间,并且它可能(以某种方式)用于重复计数或元素大小。

没有提及内存池或任何暗示这可能是一个考虑因素。

【讨论】:

  • 那么在那些实现上std::vector<int, std::allocator<int> > 不起作用?
  • @Mehrdad:他们会工作的。这是一种优化可以由编译器执行,以分配额外的空间,以供(编译器生成的)专用查找代码使用。
  • 很难说没有实际的实现。
  • 那么我认为你误解了标准的那部分内容......许多现有的实现将数组中元素的数量存储在与返回指针的已知偏移量处,以便delete[] 可以找到该数字并调用析构函数以获得适当数量的元素 - 这解释了您提到的标准的所有部分。您的“专用数组索引”和“专用表”听起来像是虚拟地址空间中数组的一些跨内存段碎片,这是一个完全不同的前景,标准没有提到。
  • @david.pfx:我不需要你的祝福来评论你的答案......你在一个公共论坛上要求澄清/参考,礼貌的建议是另一种解释您所说的基于您的结论的证据是可能的,并且友好地提及您的答案可能被误解是本课程的标准。提供它的部分原因是为了让人们阅读您的答案并想知道其他人对此的看法。您选择在您的回答中明确拒绝我的回答(读者可能看不到),而不是发表评论 - 每个人都有自己的看法。
【解决方案4】:

我确信有合适的用例需要单独的 new[]new,但我还没有遇到过这种分离唯一可能的用例。

但是,我是这样看的:由于用户调用了不同版本的运算符new,如果他们只定义一个,C++ 标准就会犯下肆意和故意丢失信息 operator newnewnew[] 都在那里转发。这里有(字面意思)一点信息,可能某人有用,我认为委员会中的人不会凭良心扔掉它!

此外,必须实现额外的new[] 对我们其他人来说是一个非常非常小的不便,如果有的话,那么保留一点信息的权衡胜过必须实现一个简单的函数我们计划的一小部分。

【讨论】:

    【解决方案5】:

    C++ 编程语言:特别版第 423 页说

    _operator new()operator delete() 函数允许用户接管单个对象的分配和解除分配; operator new[]()operator delete[]() 在数组的分配和解除分配方面发挥着完全相同的作用。

    感谢 Tony D 纠正我对这种细微差别的误解。

    哇,我并不经常被 C++ 中的某些东西抓住,我非常确定 - 我一定在 Objective-C 上花费了太多时间!

    原来的错误答案

    很简单 - 经典 C 数组的每个元素上的新 [] 形式 invokes the constructor

    所以它首先为所有对象分配空间,然后迭代调用每个槽的构造函数。

    【讨论】:

    • 您正在考虑new expression,而不是operator new。微妙的不同。
    • 我对你的回答感到困惑。我认为 operator new 用于定义使用 new 表达式传递的效果。你是说cplusplus.com上的链接也错了?
    • @AndyDent:通常是(我更喜欢 cppreference 或标准本身),但在这种情况下它是正确的:“在数组类型上使用 new 运算符的表达式,首先调用函数 operator new(即,这个函数)[...],如果成功,它会自动初始化或构造数组中的每个对象(如果需要)。”请注意,它是如何表示 new 表达式负责调用运算符然后执行您描述的构造....
    • 在检查圣经后编辑答案以符合托尼的理解
    猜你喜欢
    • 2011-08-30
    • 2021-09-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-25
    • 2011-01-11
    相关资源
    最近更新 更多