7

好吧,如果这感觉像是对旧问题的重复,我很抱歉,我已经在 tanenbaum 的现代操作系统书 Stack Overflow 上回答了几个问题,并且仍然需要清除我对此的疑问。

首先,我将不胜感激任何我应该更详细地阅读以更好地理解这种结构的书籍/资源。我不明白这些是操作系统书籍、编程语言或架构书籍中通常解释的概念。

在我提出问题之前,我将根据有关堆栈/堆的读数列出我的发现

  • 仅包含所有实例变量、动态分配(new/malloc)和全局变量
  • 不再使用数据结构堆,使用更复杂的结构
  • 通过内存位置访问,负责在其上分配的内存的单个进程
  • 碎片整理和内存分配由操作系统完成(如果是或否,请回答我关于谁管理堆、操作系统或运行时环境的问题)
  • 在进程中可以访问其引用的所有线程之间共享

  • 仅包含所有局部变量。(在函数调用时推送)
  • 使用实际的堆栈数据结构进行操作
  • 由于连续性,访问速度更快

现在,对于我的一些关于相同的问题。

  1. 全局变量,它们在哪里分配?(我认为它们是在堆上分配的,如果是这样,它们何时被分配,在运行时或编译时,还有一个问题,是否可以清除该内存(如使用删除)?)
  2. 堆的结构是什么?堆是如何组织的(它是由操作系统管理还是由运行时环境管理(由 C/C++ 编译器设置))。
  3. 堆栈是否保存方法及其局部变量?
  4. 每个应用程序(进程)都有一个单独的堆,但是如果超过堆分配,那么是否意味着操作系统无法分配更多内存?(我假设内存不足会导致操作系统重新分配以避免碎片)
  5. 进程中的所有线程都可以访问堆(我相信这是真的)。如果是,所有线程都可以访问实例变量、动态分配的变量、全局变量(如果它们有引用)
  6. 不同的进程,不能互相访问堆(即使传递了地址)
  7. 堆栈溢出崩溃
    • 仅当前线程
    • 当前进程
    • 所有进程
  8. 在 C/C++ 中,是否在运行时为函数内的块变量在堆栈上分配内存(例如,如果代码的子块(例如 For 循环)创建了一个新变量,则该变量是在运行时分配的堆栈(或堆)还是预先分配的?)它们何时被删除(块级范围,如何维护)。我对此的看法是,所有对堆栈的添加都是在运行时在块开始之前进行的,每当到达该块的末尾时,所有添加到该点的元素都会被推送。
  9. CPU 对堆栈寄存器的支持仅限于堆栈指针,该堆栈指针可以通过对内存的正常访问来递增(弹出)和递减(推送)。(这是真的?)
  10. 最后,OS/Runtime 环境生成的堆栈和堆结构是否都存在于主内存中(作为抽象?)

我知道这很多,而且我似乎一直很困惑,如果你能指出我正确的方向来解决这些问题,我将不胜感激!

4

2 回答 2

8
  1. 全局变量分配在编译时布局的内存的静态部分中。这些值在输入之前在启动期间被初始化main。当然,初始化可以在堆上分配(即静态分配std::string的结构本身将位于静态布局的内存中,但它包含的字符串数据在启动期间分配在堆上)。这些东西在正常程序关闭期间被删除。在此之前您无法释放它们,如果您愿意,您可能希望将值包装在指针中,并在程序启动时初始化指针。

  2. 堆由分配器库管理。C 运行时附带了一个,但也有自定义的,例如tcmallocjemalloc,您可以使用它们来代替标准分配器。这些分配器使用系统调用从操作系统获取大量内存页面,然后在您调用 malloc 时为您提供这些页面的一部分。堆的组织有点复杂,并且在分配器之间有所不同,你可以在他们的网站上查看它们是如何工作的。

  3. 是的。尽管您可以使用库函数,例如alloca在堆栈上腾出一块空间,然后将其用于您想要的任何东西。

  4. 每个进程都有一个单独的内存空间,即它认为它是孤独的,没有其他进程存在。通常,如果您要求,操作系统会给您更多内存,但它也可以强制限制(如ulimit在 linux 上),此时它可以拒绝给您更多内存。碎片对于操作系统来说不是问题,因为它在pages中提供内存。然而,你的进程中的碎片可能会导致你的分配器请求更多的页面,即使有空的空间。

  5. 是的。

  6. 是的,但是通常有特定于操作系统的方法来创建多个进程可以访问的共享内存区域。

  7. 堆栈溢出本身不会使任何东西崩溃,它会导致将内存值写入可能包含其他值的位置,从而破坏它。对损坏的内存进行操作会导致崩溃。当您的进程访问未映射的内存时(请参阅下面的注释),它会崩溃,不仅仅是线程,而是整个进程。它不会影响其他进程,因为它们的内存空间是隔离的。(这在 Windows 95 等旧操作系统中并非如此,所有进程共享相同的内存空间)。

  8. 在 C++ 中,堆栈分配的对象在进入块时创建,并在退出块时销毁。虽然堆栈上的实际空间可能分配得不那么精确,但构造和销毁将在这些特定点进行。

  9. x86 进程上的堆栈指针可以任意操作。编译器通常会生成代码,只需将空间量添加到堆栈指针,然后为堆栈上的值设置内存,而不是执行一堆推送操作。

  10. 进程的栈和堆都存在于同一个内存空间中。

概述内存的组织方式可能会有所帮助:

  • 你有内核看到的物理内存。
  • 当进程请求时,内核将物理内存页面映射到虚拟内存页面。
  • 一个进程在自己的虚拟内存空间中运行,不知道系统上的其他进程。
  • 当一个进程启动时,它会将可执行文件的部分(代码、全局变量等)放入其中的一些虚拟内存页面中。
  • 分配器从进程中请求页面以满足 malloc 调用,此内存构成堆。
  • 当一个线程启动(或进程的初始线程)时,它会向操作系统询问构成堆栈的几页。(您也可以询问您的堆分配器,并将它提供给您的空间用作堆栈)。
  • 当程序运行时,它可以自由访问其地址空间、堆、栈等所有内存。
  • 当您尝试访问未映射的内存空间区域时,您的程序会崩溃。(更具体地说,您会从操作系统获得一个信号,您可以选择处理该信号)。
  • 堆栈溢出往往会导致您的程序访问这些未映射的区域,这就是堆栈溢出往往会使您的程序崩溃的原因。
于 2013-01-24T03:39:48.320 回答
2
  1. 分配全局变量的位置实际上取决于系统。有些系统会将它们静态地放在二进制文件中,有些系统会将它们分配到堆上,有些系统会将它们分配到堆栈上。如果一个全局变量是一个指针,你可以delete使用它指向的值,但是没有办法清除该内存,否则。应用程序退出时将自动调用全局变量的析构函数(好吧,也许不是使用 SIGTERM)
  2. 我不是很肯定,但我想它是由操作系统管理的,特别是内核。
  3. 是的,而且仅限于某一点。例如,您不能进行无限递归,因为值会(没有双关语)叠加。你会得到一个,等待它,堆栈溢出(AHH,就是这样,他说的!)
  4. 某些操作系统可能会通过单独的进程施加堆大小限制,但通常如果您未能分配内存,那是因为没有剩余内存了。
  5. 所有线程共享一个公共堆,所​​以是的,它们都可以访问全局变量、动态分配等。
  6. 通常是正确的,尽管在一些非常简单的架构上这可能不是真的。在大多数情况下,操作系统在虚拟表的上下文中执行进程,因此您使用的指针值实际上指向的内存地址与它们看起来的不同。
  7. 当前进程,如果按进程表示操作系统级进程。
  8. 我假设这是正确的,但我不知道自己。
  9. 这个不在我的驾驶室里。
  10. 是的,有点。正如我之前提到的,大多数操作系统使用 vtables 将进程指针映射到主内存。另外,考虑分页到磁盘(交换)
于 2013-01-24T03:38:25.843 回答