内存管理 - C/C++ 中的并发编程、堆栈和堆

memory management - Concurrent Programming, Stacks and Heaps in C/C++

本文关键字:编程 并发 堆栈 管理 C++ 内存      更新时间:2023-10-16

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

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

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

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

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

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

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

我知道这很多,而且我似乎自始至终都很困惑,如果您能指出正确的方向来弄清楚这些事情,我将不胜感激!

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

  2. 堆由分配器库管理。C运行时附带了一个,但也可以使用tmalloc或jemalloc等自定义版本来代替标准分配器。这些分配器使用系统调用从操作系统获取大页面内存,然后在调用 malloc 时为您提供这些页面的一部分。堆的组织有些复杂,并且因分配器而异,您可以在其网站上查找它们的工作方式。

  3. 是的。尽管您可以使用像alloca这样的库函数在堆栈上腾出一大块空间,并将其用于您想要的任何内容。

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

  5. 是的。

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

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

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

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

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

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

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