C ++中多线程进程的内存布局

时间:2010-09-05 22:35:11

标签: java c++


我对在多线程进程中如何排列堆栈和堆有点困惑:
1)每个线程都有自己的私有堆栈 2)所有线程共享堆 3)当程序动态创建线程(例如Java中的新线程())时,对象在堆上分配。

堆也包含线程对象的内存,这意味着堆包含堆栈(属于线程)?

3 个答案:

答案 0 :(得分:4)

因为我们不愿意限制线程软件的实施者。

  

每个线程都有自己的私有堆栈。

当每个线程执行一组函数互为独立时,它们需要存储返回地址等,因此每个线程都需要自己的堆栈。

  

所有线程共享堆

这是实施它的最简单方法。这也意味着所有线程共享一个共同的内存块,这样每个线程只需通过修改内存就可以与其他线程通信。

  

当程序动态创建线程(例如Java中的新线程())时,对象将在堆上分配。

你在问题​​1中提到的堆栈。我们需要为它保留内存。所以我们分配一大块的堆给它给线程并说使用这块内存来实现你的堆栈。 (并不是说它是这样做的,但这是一种简单的技术)。

  

堆也包含线程对象的内存,这意味着堆包含堆栈(属于线程)?

在单线程程序中,有空间将堆栈实现为堆的块。堆栈和堆的概念是分开的并且相互增长就是这样的;一个概念。未定义如何实现它们并且它们不是我们无法在堆内实现堆栈的原因。有关详细信息,请参阅此问题:stack growth direction

答案 1 :(得分:1)

将“堆栈”视为与任何其他数据结构一样的数据结构。它可以通过多种方式实现。

以下是2000年左右之前C和C ++程序中堆栈典型实现的描述。大多数人仍然这样做:

  

存在一系列连续的内存地址,称为“堆栈”。通常,在具有内存控制器的系统上(对于Intel,这意味着80386和更新的内容),此内存地址范围的页面在使用之前不会分配给物理内存。通常,这个连续的地址范围发生在地址空间的末尾。

     

有一个堆栈指针通常从内存区域的末尾开始。创建新的堆栈帧时,堆栈指针会减小帧的大小。 CPU具有专门为此操作设计的指令。如果访问的内存区域没有分配给它的任何物理内存,则OS会处理页面错误并找到一些内存以分配给现在使用的页面。

     

未在寄存器中传递的所有局部变量和函数参数都会进入堆栈帧。

     

对于多线程程序,此方案不起作用,因此通常使用mallocnew分配内存区域,并使用指向该区域的指针启动一个新线程。记忆及其大小。如果新线程需要的堆栈空间比你分配的多,那么可能会发生各种可怕的事情,包括线程只是在一些随机内存上踩踏,其中包括在堆上分配的其他变量。

但是,到目前为止,这不是实现堆栈的唯一方法。例如,您可以将堆栈实现为链接列表,列表的每个节点都是堆栈帧。支持名为“continuations”的构造的语言经常这样做。事实上,他们通常使用DAG作为单个堆栈帧可能会产生多个同时有效的其他堆栈帧。

可以做的另一件事就是介于两者之间,你的节点只是大的内存区域,每个区域都包含几个堆栈帧。当创建一个超出节点的新帧时,另一个节点将被分配。

或者,所有局部变量都可以使用new或类似的东西进行分配,并在它们超出范围时被销毁。编译器可以在幕后实现这一点。

因此,担心堆栈的确切位置或内存的分配方式,特别是像Java这样甚至没有C或C ++指针的语言,都有点愚蠢。它甚至可能在不同的完全兼容的JVM之间变化。

我要说一般来说,C ++中的pthreads以我在本节的最后一段中描述的多线程程序的方式实现堆栈,其中我描述了C和C ++历史上的工作方式。它们通常还有一个“保护页面”,它是在为堆栈分配的区域的开头有意未映射的页面,因此用尽堆栈空间的程序通常是SEGV。 (实际上,这显然是对错误的过度简化,请参阅Ben Voigt关于实际使用警卫页面的评论)。

答案 2 :(得分:0)

每个堆栈都是在堆上进行的,只有少量内核从真正的“一个”堆栈运行。