【问题标题】:Where are variables in C++ stored?C ++中的变量存储在哪里?
【发布时间】:2010-09-18 20:06:26
【问题描述】:

C++ 中的变量存储在哪里?

在 RAM 或处理器的缓存中?

【问题讨论】:

  • 这个问题没有意义,因为(大多数)缓存是透明的,实际上只是内存系统的一部分。它也有缺陷,因为它实际上取决于存储 C++(或任何编译语言)变量的架构和编译器。
  • 另外,题名也可以大大改进
  • @Tal,正如其他人所说,问题有点模糊。也许你想看看人们的cmets,看看你是否可以问一个更具体的问题。
  • 我们可以对不应该问的问题做些什么?我可以用“Mu”标记它们吗?
  • 好的,我已经完成了。 en.wikipedia.org/wiki/W%C3%BA

标签: c++ architecture memory computer-architecture


【解决方案1】:

变量可以存放在多个不同的地方,有时甚至不止一个地方。加载程序时,大多数变量都放在 RAM 中;有时,声明为const 的变量被放置在 ROM 中。每当访问一个变量时,如果它不在处理器的缓存中,就会导致缓存未命中,并且当变量从 RAM/ROM 复制到缓存中时,处理器将停止。

如果您有任何不错的优化编译器,局部变量通常会存储在处理器的寄存器文件中。变量在读写时会在 RAM、缓存和寄存器文件之间来回移动,但它们通常总是在 RAM/ROM 中有一个副本,除非编译器认为没有必要这样做。

【讨论】:

  • 普通非嵌入式架构的编译器不会将变量放在“ROM”中。
  • ROM 通常表示仅在制造期间写入的内存 - const 变量仍存储在计算机的 RAM 中,但在程序执行期间不会写入
  • Stroustrup 经常谈到存储在 ROM 中的变量。 C++ 标准委员会 (open-std.org/jtc1/sc22/wg21/docs/PDTR18015.pdf pg. 75) 也是如此。实际上,它不是物理 ROM,而是可执行文件的一部分(在 ELF 中是 .text 部分)。
【解决方案2】:

C++ 中的变量存储在堆栈或堆中。

堆栈:

int x;

堆:

int *p = new int;

话虽如此,两者都是内置于 RAM 中的结构。

如果您的 RAM 使用率很高,但 Windows 可以将其交换到磁盘。

当对变量进行计算时,内存将被复制到寄存器中。

【讨论】:

    【解决方案3】:

    根据它们的声明方式,它们将存储在“heap”或“stack”中

    堆是应用程序可以使用的dynamic 数据结构。

    当应用程序使用数据时,必须在使用数据之前将其移至 CPU 的寄存器,但这是非常易失的临时存储。

    【讨论】:

      【解决方案4】:

      存储命名变量:

      • 在堆栈上,如果它们是函数局部变量。
        C++ 将此称为“自动存储”1,并不要求它实际上是 asm 调用堆栈,在一些罕见的实现中它不是。但在主流实现中确实如此。
      • 如果它们是全局的或static,则在每个进程的数据区域中。
        C++ 称之为“静态存储类”;它在 asm 中通过在 section .data.bss.rodata 或类似文件中放置/保留字节来实现。

      如果变量是一个用int *p = new int[10];或类似初始化的指针,指针变量p将像上面那样进入自动存储或静态存储。内存中的指向对象是:

      • 在堆上(C++ 称之为动态存储),分配有newmalloc
        在 asm 中,这意味着调用一个分配器函数,如果它的空闲列表为空,它最终可能会通过某种系统调用从操作系统获取新内存。 “堆”不是现代操作系统/C++ 实现中的单个连续区域。

      C 和 C++ 不进行自动垃圾回收,命名变量本身不能在动态存储(“堆”)中。动态存储中的对象是匿名的,除了被其他对象指向之外,其中一些可能是适当的变量。 (结构或类类型的对象,与 int 这样的原始类型相反,可以让您在此匿名对象中引用命名的类成员。在成员函数中,它们甚至看起来相同。)

      这就是为什么您不能(安全/有用地)返回指针或对局部变量的引用。


      当然,这些都在 RAM 中。缓存对用户空间进程是透明的,尽管它可能会明显影响性能。

      编译器可以优化代码以将变量存储在寄存器中。这是高度依赖编译器和代码的,但是好的编译器会积极地这样做。


      脚注 1:有趣的事实:auto 在 C++03 及更早版本中,仍在 C 中,meant automatic storage-class,但现在 (C++11) 它推断类型。

      【讨论】:

      • 其实变量并不存储在堆中。你可能有一个变量指向堆中的某个东西,但变量本身将在寄存器中、堆栈上或静态分配。
      • 克里斯托弗,一个有效的观点。在 C++ 定义中,变量是指针,而不是指向的数组,所以你是对的。
      • 请注意,将变量存储在寄存器中也高度依赖于平台。不同的架构有不同数量的寄存器,而且并非所有架构中的所有寄存器都相同。
      • @Kristopher:嗯,你的说法并不完全正确。类对象的成员变量 存储/分配在堆上。
      • @Chethan:你的意思是如果整个类对象本身是在堆上分配的?是的,这是可能的,但不是必需的。在 C++ 中,您可以在本地或全局范围内拥有一个 myclass foo = { ... }; 变量,因此类对象本身(以及它的所有成员)处于自动(堆栈)或静态存储中,而不是动态(堆)存储。使用动态分配的存储来存储具有命名子对象的聚合对象与使用指向此类内存的指针(如具有编号元素的数组)的方式没有根本不同。你仍然只能通过一些指针或引用来获得它。
      【解决方案5】:

      变量通常存储在 RAM 中。这要么在堆上(例如全局变量、方法/函数中的静态变量),要么在堆栈上(例如在方法/函数中声明的非静态变量)。 Stack 和 Heap 都是 RAM,只是位置不同。

      指针有点特殊。指针本身遵循上述规则,但它们指向的数据通常存储在堆上(使用malloc 创建的内存块,使用new 创建的对象)。但是您可以创建指向堆栈内存的指针:int a = 10; int * b = &a;; b指向a的内存,a存放在栈上。

      进入 CPU 缓存的内容超出了编译器的控制范围,CPU 自行决定缓存什么以及缓存多长时间(取决于“此数据最近是否被使用过?”或“ 预计数据很快就会再次使用吗?")当然缓存的大小也有很大的影响。

      编译器只能决定哪些数据进入 CPU 寄存器。通常,如果经常连续访问数据,则数据会保留在那里,因为寄存器访问速度比缓存快,而且比 RAM 快得多。某些系统上的某些操作实际上只有在数据在寄存器中时才能执行,在这种情况下,编译器必须在执行操作之前将数据移动到寄存器,并且只能决定何时将数据移回 RAM。

      编译器将始终尝试将最常访问的数据保存在寄存器中。当一个方法/函数被调用时,通常所有的寄存器值都会被写回 RAM,除非编译器可以确定被调用的函数/方法不会访问数据来自的内存。同样在返回方法/函数时,它必须将所有寄存器数据写回 RAM,否则新值将丢失。在某些 CPU 架构上,返回值本身在寄存器中传递,否则通过堆栈传递。

      【讨论】:

        【解决方案6】:

        对于一般的 C++,正确的答案是“无论您的编译器决定将它们放在哪里”。除非您以其他方式指示编译器,否则您不应做出假设。有些变量可以完全存储在寄存器中,有些可能会被完全优化掉并在某处被文字替换。对于某些平台上的某些编译器,常量实际上可能最终在 ROM 中。

        关于“处理器的缓存”的部分问题有点混乱。有一些工具可以指导处理器如何处理其缓存,但一般来说,这是处理器的事情,您应该不可见。您可以将缓存视为 CPU 进入 RAM 的窗口。几乎任何内存访问都会通过缓存。

        另一方面,在大多数操作系统上,未使用的 RAM 有时会被换出到磁盘。所以它可能(但不太可能)在某些时候你的变量实际上存储在磁盘上。 :-)

        【讨论】:

        • 我知道编译器可以决定做任何事情。目前是否有编译器做一些与通常非常不同的事情(自动=堆栈或寄存器,分配=帮助等)?
        • @user231536:对于像 PIC 和 8051 这样的架构,很难将调用堆栈用于标准 C 模型,是的,显然存在一些编译器,它们会将自动存储类变量放入静态存储中。 (如果你愿意,你必须特别声明函数是可重入的。) Supercat 在Why do C to Z80 compilers produce poor code? 中对此进行了评论,一般的问答中充满了不容易映射到 asm 的 C 示例。 (有些 asm 与现代优化编译器所能做的相比简直是糟糕透顶。)
        【解决方案7】:

        我认为您混淆了两个概念。一、C++语言如何在内存中存储变量。第二,计算机和操作系统如何管理该内存。

        在 C++ 中,变量可以在堆栈上分配,堆栈是为程序使用而保留的内存,并且在线程启动时大小固定,或者在动态内存中可以使用 new 动态分配。如果代码分析允许,编译器还可以选择将变量存储在处理器中的寄存器上。这些变量永远不会看到系统内存。

        如果变量最终进入内存,操作系统和处理器芯片组就会接管。基于堆栈的地址和动态地址都是虚拟的。这意味着它们可能在任何给定时间都驻留在系统内存中,也可能不驻留在系统内存中。 in memory 变量可以存储在系统内存中,分页到磁盘上,或者可以驻留在处理器上或处理器附近的高速缓存中。因此,很难知道这些数据实际存在于何处。如果一个程序有一段时间没有空闲,或者两个程序正在竞争内存资源,则可以将该值保存在页面文件中的磁盘中,并在程序轮到运行时恢复。如果变量对于正在完成的某些工作是本地的,则可以在处理器缓存中对其进行多次修改,然后最终将其刷新回系统内存。您编写的代码永远不会知道发生了这种情况。它所知道的只是它有一个可以操作的地址,而所有其他系统都会负责其余的工作。

        【讨论】:

        • 在大多数现代系统中,堆栈的大小不是固定的,而是由操作系统在发生页面错误时自动扩展(因为堆栈为空)。
        • 在您的回答中,很明显发生了两种不同的事情:语言的“对象模型”和 RAM/SwapFile/Caching 系统。不错的答案!
        • 嗨,保罗。感谢您的评论。你说得对,堆栈是虚拟内存,可以分页。我的观点是,在线程启动时分配它的大小是固定的。这由链接器控制。
        • Linux 上的堆栈大小(对于“主”线程的堆栈)受到进程从其父进程继承的ulimit -s 设置的限制,not 由链接器设置。此外,整个大小在进程启动时没有在技术上映射(因此在/proc/self/maps 中甚至只显示了一个小的初始大小),更不用说连接到硬件页表中了。但它是保留的,因此mmap(MAP_ANONYMOUS) 等其他映射不会窃取它。触摸堆栈指针下方的内存会触发映射的自动扩展。 What is "automatic stack expansion"?
        • 这种扩展与请求分页分配的堆栈内存是分开的,后者发生在堆分配和 BSS 中。 (新的堆页面和进程启动时的 BSS 通常在写入时复制映射到单个零物理页面,因此读取不会分配新的物理页面。但是虚拟地址空间 是 i> 完全映射。)
        【解决方案8】:

        C++ 不知道处理器的缓存。

        当您运行以 C++ 或任何其他语言编写的程序时,您的 CPU 会在缓存中保留一份“流行” RAM 块的副本。这是在硬件级别完成的。

        不要将 CPU 缓存视为“其他”或“更多”内存...它只是一种将一些 RAM 块保持在附近的机制。

        【讨论】:

          【解决方案9】:

          C++语言通过C++程序中的变量支持两种内存分配方式:

          静态分配是在声明静态或全局变量时发生的。每个静态或全局变量定义一个固定大小的空间块。当您的程序启动(执行操作的一部分)时,空间被分配一次,并且永远不会被释放。 当您声明一个自动变量(例如函数参数或局部变量)时,就会发生自动分配。自动变量的空间在输入包含声明的复合语句时分配,并在退出该复合语句时释放。自动存储的大小可以是变化的表达式。在其他 CPP 实现中,它必须是一个常数。 第三种重要的内存分配,动态分配,不受 C++ 变量支持,但可用的库函数。 动态内存分配

          动态内存分配是一种技术,程序在运行时确定在哪里存储一些信息。当您需要的内存量或继续需要多长时间取决于程序运行之前未知的因素时,您需要动态分配。

          例如,您可能需要一个块来存储从输入文件中读取的行;由于行的长度没有限制,因此您必须动态分配内存,并在读取更多行时使其动态变大。

          或者,您可能需要为输入数据中的每条记录或每个定义设置一个块;由于您无法提前知道会有多少,因此您必须在阅读时为每条记录或定义分配一个新块。

          当您使用动态分配时,内存块的分配是程序明确请求的操作。当您想要分配空间时调用函数或宏,并使用参数指定大小。如果要释放空间,可以通过调用另一个函数或宏来实现。您可以随时随地进行这些操作。

          CPP 变量不支持动态分配;没有“动态”存储类,也永远不可能有一个 CPP 变量的值存储在动态分配的空间中。获得动态分配内存的唯一方法是通过系统调用,而引用动态分配空间的唯一方法是通过指针。由于不太方便,而且动态分配的实际过程需要更多的计算时间,程序员一般只在静态分配和自动分配都不起作用时才使用动态分配。

          例如,如果您想动态分配一些空间来保存 struct foobar,则不能声明其内容是动态分配的空间的 struct foobar 类型的变量。但是您可以声明一个指针类型的变量 struct foobar * 并为其分配空间地址。然后你可以在这个指针变量上使用运算符'*'和'->'来引用空间的内容:

           {
             struct foobar *ptr
                = (struct foobar *) malloc (sizeof (struct foobar));
             ptr->name = x;
             ptr->next = current_foobar;
             current_foobar = ptr;
           }
          

          【讨论】:

            猜你喜欢
            • 2018-02-01
            • 2010-09-10
            • 2021-02-08
            • 2017-03-26
            • 2010-10-02
            • 2011-06-07
            相关资源
            最近更新 更多