为什么基本引用没有堆开销?

7

StroustrupC++基础书籍中,他提供了一种纯面向对象的语言(第4页)。

class complex { double re, im; /* … */ };
complex a[ ] = { {1,2}, {3,4} };

他假设在纯面向对象语言中,a 是分配在堆上的,并且 a 的内存布局如下所示: enter image description here 引用占一个字,堆开销占两个字,浮点数占四个字,据此估算出 a 的大小为3*sizeof(reference)+3*sizeof(heap_overhead)+4*sizeof(double)。假设引用占一个字,堆开销占两个字,则得到一个可能的大小为19个字,与 C++ 的 8 个字进行比较。这种内存开销会带来运行时开销,因为要对元素进行间接访问和分配。这种间接访问通常会导致缓存利用问题,并限制 ROM 可能性。
我注意到最上面的引用没有堆开销(白色矩形)。
我猜这是一般现象,而不是针对纯 OO 示例语言指定的。
但我找不到任何参考资料(我承认这不是一个搜索引擎友好的问题)。

更新

感谢您的回答。然而,我忘记发布我的原始猜测。(抱歉,这是我的错。
实际上,我也认为因为 a 本身可能被分配在堆栈或其他地方,它本身不会有堆开销。但后来,我注意到 BS 也说:
“与“纯面向对象语言”中的更典型布局相比,每个用户定义的对象都单独分配在堆上并通过引用访问...”(第4页)。
所以,我认为他将实现限制为仅在堆上(或无堆栈)。
(当然,也许我对这个句子读得太多了。)

一个程序总是会使用堆和栈。两者都应该存在。 - Joseph D.
@codekaizer 在历史上存在着无栈和无堆的设计。https://dev59.com/BnM_5IYBdhLWcg3wslfs#50479025 - Chen Li
@陳力 談到了像Java這樣的語言,其中所有變量都是指針/引用,沒有例外。在所有無堆棧機器上,堆棧首先被實現。請注意,當創建C時,沒有計算機具有基於CPU的堆棧。調用堆棧是模擬的。沒有任何一種類型的堆棧,您都無法調用函數。正式的C++文檔談論存儲類型和持續時間,而不是關於如何實現該存儲的方式。 - Swift - Friday Pie
@Swift-FridayPie 谢谢! - Chen Li
4个回答

3
顶层引用的位置取决于它在哪里。以下是几个选项:
  1. 它可以存在于堆栈中。代码中的a可以表示为这样。
  2. 它被嵌入到现有对象中。
  3. 内存被分配来仅持有句柄。在这种情况下,它将具有内存开销。
  4. 它作为全局实体存在。
  5. 引用可以存在于寄存器中。在代码中的a也可以表示为这样,在这种情况下,它既没有堆开销也没有内存开销:而是“仅仅”需要使用一个寄存器。
在这种情况下的主要意识是,这些引用不是实际对象,即你不能有对引用的引用。因此,没有必要保持引用的位置固定。它们可以(并且当它们实际上被表示时)是实际值。引用的值在典型实现中是对象的地址。
其他实体是对象:
  • complex用于创建实际嵌入两个奇数double“对象”的对象,这些值也是值而不是实际对象。
  • a是此模型中引用两个complex对象的数组对象。

请查看我在问题中的更新。谢谢:) - Chen Li

2

当你将某些内容存储在堆上时,会产生堆开销。两个复杂值存储在堆上,因此它们会有开销。引用数组也存储在堆上,因此它会有开销。

但是,对于数组的引用本身不存储在堆上。通常,该引用将作为局部变量放置在堆栈上,或者堆栈存储可能通过使用CPU寄存器而被优化掉。无论哪种情况,引用本身只是一个本地指针变量,它本身没有堆分配开销。


谢谢,cmaster。事实上,在发布这个问题之前,我自己也有这个答案。但后来,我看到BS也说:每个用户定义的对象在堆上单独分配(第4页)。a 本身也是一个用户定义的对象���所以我认为他也将实现限制为仅堆(无栈)。 - Chen Li
@陳力 你无法将程序编译为无堆栈可执行文件,因为你需要一些引用到堆上才能使用它。如果不知道信息在哪里,就无法检索任何信息。在已编译的程序中,CPU有一些寄存器,允许它找到其堆栈和代码。而该堆栈包含引用(局部变量),允许它找到堆上的对象。这就是信息如何被锚定到CPU的方式,没有这种锚定,就没有办法神奇地知道东西在哪里。 - cmaster - reinstate monica
1
@cmaster,即使现在存在一些CPU架构(大多数是RISC、MIPS、51派生控制器等)不按照这种方式工作,但总有一些代码结构可以模拟这种行为,以便使用基于函数的代码结构。堆栈寄存器可能不存在,但是有一个“堆栈”数据结构来存储地址、值、返回地址等。 - Swift - Friday Pie
@Swift-FridayPie 你的意思是没有 push/pop 指令吗?是的,这种情况确实存在。在 PPC 上,你需要使用类似 stwu r1,-StackFrameSize(r1) 的调用来创建一个堆栈帧。(如果我没记错的话,r1 是按照约定用于保存堆栈指针的寄存器。从 CPU 的角度来看,它也可以是任何其他寄存器。)但事实仍然存在一个地方可以以某种方式推送返回地址。 - cmaster - reinstate monica
1
@MatteoItalia 好的,我应该说:你无法将递归程序编译成无栈可执行文件。只要您的函数不是可重入的,您可以将它们的本地变量存储在某个全局可访问的位置,并且可以将其地址硬编码到CPU指令中。在这种情况下,您确实可以没有堆栈而得到一些好处。但是,在这种设置中,您始终具有本地变量(=引用),这些本地变量本身会在没有分配开销的情况下存储在某个地方。在某些时候,即使没有堆栈,您始终有这种锚定。 - cmaster - reinstate monica
显示剩余2条评论

1
complex a[ ] = { {1,2}, {3,4} };

让我们打破这种自下而上的方法。
1.您有两个“复杂”对象,它们是“列表初始化”的:Complex {1,2}Complex {3,4}

复杂{双重,双重}

每个“complex”对象都具有堆(白色)的地址和由“double”值占用的空间(蓝色)。
2.接下来是外部的“{”括号,用于“列表初始化”一个“Complex”对象的“数组”。

数组{Complex1,Complex2}

这具有“数组”对象的地址(白色),以及对两个“Complex”对象的两个引用(蓝色)。
3.最后,对于“complex a”没有开销,因为它只是存在于堆栈中的局部变量。

请看一下我在问题中的更新。谢谢:) - Chen Li

1
这里的想法是,顶层引用要么是堆栈/参数上的本地变量/寄存器(没有堆开销),要么作为某个更大结构的一部分(该结构可能在堆上分配,但我们不计算其开销,因为它不特定于此特定引用,而是共享其他数据成员)。
“实际上,我也认为,因为a本身可以分配在堆栈或其他任何位置,所以它本身不会有堆开销。但后来,我注意到BS还说:”与从“纯面向对象语言”中更典型的布局进行比较,“每个用户定义的对象单独分配在堆上,并通过引用访问……”在第4页。
所以,我认为他将实现限制为仅适用于堆(或无堆栈)。
(当然,也许我读太多了这个句子)”
我认为你读得太多了。Stroustroup提到的现实世界语言(如Python、Java或C#-如果我们限制自己只考虑装箱类型)确实有一个“常规”堆栈,其中包含一组普通的引用数组。

谢谢:)但请看一下我在问题中的更新。 - Chen Li

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