【问题标题】:Does new always allocate on the heap in C++ / C# / Javanew 是否总是在 C++/C#/Java 中的堆上分配
【发布时间】:2011-10-21 18:36:14
【问题描述】:

我的理解一直是,无论是 C++、C# 还是 Java,当我们使用 new 关键字创建对象时,它都会在堆上分配内存。我认为 new 仅用于引用类型(类),而原始类型(int、bool、float 等)从不使用 new 并且总是在堆栈上(除非它们是成员变量使用new 实例化的类)。但是,我一直在阅读 information,这让我怀疑这个长期存在的假设,至少对于 Java 和 C# 而言。

例如,我刚刚注意到在 C# 中 new 运算符可用于初始化值类型,请参阅 here。这是否是规则的例外,语言的辅助功能,如果是,还会有哪些其他例外?

有人可以澄清一下吗?

【问题讨论】:

  • 你能告诉我们你在读什么导致这些疑问吗?
  • 我在原始问题中添加了对其中一篇文章的引用

标签: c# java c++ memory memory-management


【解决方案1】:

我认为 new 仅用于引用类型(类),而原始类型(int、bool、float 等)从不使用 new

在 C++ 中,您可以根据需要在堆上分配原始类型:

int* p = new int(42);

如果您想要一个共享计数器,这很有用,例如在shared_ptr<T> 的实现中。

此外,您不必在 C++ 中将 new 与类一起使用:

void function()
{
    MyClass myObject(1, 2, 3);
}

这将在堆栈上分配myObject。请注意,new 在现代 C++ 中很少使用。

此外,您可以在 C++ 中重载operator new(全局或特定于类),因此即使您说new MyClass,对象也不一定会在堆上分配。

【讨论】:

  • 谢谢。现代 c++ 中用什么代替了新的?
  • 好吧,new 在 C++ 中有许多不同的用途……你可以使用 std::string 而不是 new char[n]。您使用std::vector<MyClass> 而不是new MyClass[n]。如果要共享对象,请使用 auto p = make_shared<Class>() 等而不是 MyClass* p = new MyClass
  • 我想说delete 在现代 C++ 中很少使用。在某些情况下,我们可能会将new 用于shared_ptrunique_ptr
  • @Deqing 为什么?我们有make_sharedmake_unique
  • @fredoverflow 当您不希望 weak_ptr 保持共享计数不被删除时。
【解决方案2】:

我不太了解 Java(而且似乎很难获得有关它的文档)。

在 C# 中,new 调用构造函数并返回一个新对象。如果它是值类型,则分配在堆栈上(例如局部变量)或堆上(例如装箱对象、引用类型对象的成员)。如果它是引用类型,它总是在堆上并由垃圾收集器管理。详情请见http://msdn.microsoft.com/en-us/library/fa0ab757(v=vs.80).aspx

在 C++ 中,“新表达式” 返回一个指向具有 动态存储持续时间 的对象的指针(即,您必须自行销毁)。 C++标准中没有提到堆(有这个含义),而获得这种对象的机制是实现定义的。

【讨论】:

  • 在 c# 中,“new”总是返回对堆上对象的引用,这与 GC 无关
  • 恼人的是,虽然你可以在 C# 中说 int i = new int(),但你不能说 new int(1)——这是一种让我困惑的贫血伪结构,我来自 C++...我目前正在尝试进入 C#,但这些事情让我觉得没有经过深思熟虑。
  • @Kerrek SB:new int() 给你一个“自动存储”整数的事实真的让我很恼火。这与任何事情都不一致。
  • -1 用于在解释 C# 的行为时链接到 MSDN,但参考 C++ 行为的标准文档。如果您查看过C# standard,您将不会看到任何提及连接到运算符new 的“堆栈”。
  • @Alex,同意你的最后陈述。尽管如此,C# 标准在这种情况下更有用(恕我直言),因为它使用正确的思维方式来思考语言; MSDN 提出了一种不恰当的思维方式。希望 -1 不会显得太苛刻,它只是本着良好论证的精神给出的。
【解决方案3】:

我的理解一直是,无论 C++、C# 还是 Java,当我们使用 new 关键字创建对象时,它会在堆上分配内存。

你的理解有误:

  • new 在不同编程语言中的工作方式可能不同,即使这些语言表面上相似。不要让 C#、C++ 和 Java 的相似语法误导您!

  • 术语“堆”和“堆栈”(在内部内存管理的上下文中被理解)根本与所有编程语言无关。可以说,这两个概念通常是实现细节,而不是编程语言官方规范的一部分。

    (IIRC,至少 C# 和 C++ 是这样。我不了解 Java。)

    它们是如此广泛的实现细节这一事实并不意味着您应该依赖这种区别,也不意味着您甚至应该知道它! (不过,我承认我通常发现了解内部“事情是如何运作的”是有益的。)

我建议您不要过多地担心这些概念。您需要做对的重要事情是理解语言的语义;例如,对于 C# 或任何其他 .NET 语言,引用和值类型语义的差异。

示例:C# 规范对运算符 new 的说明:

注意C# specification published by ECMA (4th edition) 的以下部分没有提及任何“堆栈”或“堆”:

14.5.10 新运营商

new 运算符用于创建类型的新实例。 […]

new 操作符意味着创建一个类型的实例,但不一定意味着动态分配内存。特别是,值类型的实例除了它们所在的变量之外不需要额外的内存,并且当使用 new 来创建值类型的实例时不会发生动态分配。

相反,它谈到“动态分配内存”,但这不是一回事:您可以在堆栈、堆或其他任何地方(例如硬盘驱动器)动态分配内存.

然而,它所做说的是,值类型的实例是就地存储的,这正是值类型语义的全部内容:值类型实例在赋值期间被复制,而引用类型实例被引用/“别名”。 是要理解的重要内容,而不是“堆”或“堆栈”!

【讨论】:

  • 请向那些在 C# 中问我堆与堆栈问题的面试官提出这个建议:)
【解决方案4】:

在 c# 中,class 始终存在于堆上。 struct 可以在堆 堆栈上:

  • 变量(捕获块和迭代器块除外)和结构上的字段本身在堆栈上存在于堆栈中
  • 捕获、迭代器块、堆上某些内容的字段,以及数组中的值位于堆上,“装箱”值也是如此

【讨论】:

  • 愚蠢的问题:C#(语言)真的“堆栈”和“堆”的概念吗?我检查了标准(第一版),找不到任何参考,OP 链接的 MS 网站上也没有。 C++ 确实没有规定任何特定的内存实现。
  • @Kerrek SB:MSDN 在他们关于 new 的 C# 文章中明确声明了“堆”和“堆栈”。我知道这不是 ECMA 文档,但由于它们制作语言并且是参考实现/文档,这对我来说意味着 C# 中有一个堆栈和堆。
  • 来自Eric Lippert's blog的相关文章:堆栈是一个实现细节Part 1Part 2 .
  • @stakx:谢谢!亚历克斯:这有点令人担忧。如果标准是人们唯一应该考虑的事情,为什么 MS 会遇到这些麻烦来推动标准通过?抽象地讨论 C# 没有优点吗? (我猜 SO 没有专门的“MS-C#”标签……)
  • @Alexandre,Eric Lippert(我相信他知道他的东西)在他博客上的一篇文章中提到,不幸的是,MSDN 经常明确地谈论“堆栈”和“堆” ,因为(IIRC)相关规范实际上并不要求这些。它们应该被视为一个实现细节。参见例如我链接到的文章。
【解决方案5】:

根据http://download.oracle.com/javase/7/docs/technotes/guides/vm/performance-enhancements-7.html,Java 7 会进行转义分析以确定是否可以在堆栈上分配对象。

但是,您不能指示运行时在堆或堆栈上分配对象。它是自动完成的。

【讨论】:

  • Java 6 实际上也有逃逸分析。它是在我认为 u14 中作为一个选项引入的,并在以后的更新中设为默认值。甚至有一个特定的性能构建(大约 u7 orso)进行了逃逸分析。
【解决方案6】:

关于 c#,请阅读 The Truth About Value Types。 你会看到值类型也可以放在堆上。

并且at this question 建议引用类型可以进入堆栈。 (但目前还没有发生)

【讨论】:

    【解决方案7】:

    (参考Java)你说的是正确的 - 原语是在堆栈上分配的(有例外,例如闭包)。但是,您可能指的是以下对象:

    Integer n = new Integer(2);
    

    这指的是一个 Integer 对象,而不是原始 int。也许这是你困惑的根源?在这种情况下,n 将被分配到堆上。也许您的困惑是由于autoboxing 规则?另请参阅this 问题以获取有关自动装箱的更多详细信息。查看此答案中的 cmets,了解在堆上分配原语的规则的例外情况。

    【讨论】:

    • 我知道 Integer 是引用类型而 int 是原始类型,但感谢您的帮助。
    • 我不知道 c#,但是在 Java 6 中,像 int myInt = new int() 这样的东西不会编译。
    • 在 Java 中,如果原语是字段,则不会在堆栈上分配它们,只有当它们是本地时。在 C# 中,如果结构是局部变量或方法参数并且它们没有被提升到 lambda 闭包中,则它们会在堆栈上分配。我很确定还有其他情况会导致值类型进入堆,但我现在想不出。
    • 在 c# 中,原语可以非常多地存在于堆上——我已经在我的回答中介绍了这些场景
    • 哦!迭代器块。他们也将所有当地人都提升到堆中。
    【解决方案8】:

    在 Java 和 C# 中,我们不需要在堆上分配原始类型。它们可以在堆栈上分配(而不是限制在堆栈上)。而在 C++ 中,我们可以在堆栈和堆上分配原始类型和用户定义类型。

    【讨论】:

    • 答案有什么问题?只是想知道。否决票没有问题。
    【解决方案9】:

    在 C++ 中,还有另一种使用 new 运算符的方法,那就是通过“placement new”。你指向的内存可能存在于任何地方。

    What uses are there for "placement new"?

    【讨论】:

    • 您可能指的是“placement-new 表达式”,而不是运算符(运算符实际上是无操作)。新 表达式 的关键特性是调用构造函数。
    猜你喜欢
    • 2011-05-30
    • 1970-01-01
    • 2020-03-30
    • 2011-06-17
    • 2010-09-17
    • 1970-01-01
    • 1970-01-01
    • 2011-03-06
    • 2011-05-12
    相关资源
    最近更新 更多