它如何知道我的价值在记忆中的位置?

时间:2009-09-03 13:19:01

标签: variables memory memory-management language-agnostic

当我编写一个程序并告诉它int c=5时,它会将值5放入它的内存中,但它如何记住哪一个?我能想到的唯一方法就是有另外一点记忆来告诉它,但是它必须记住它保存在哪里,所以它如何记住一切都在哪里?

11 个答案:

答案 0 :(得分:13)

您的代码在执行之前被编译,在该步骤中,您的变量将被存储值的空间的实际引用替换。

这至少是一般原则。实际上,它将更加复杂,但仍然是相同的基本理念。

答案 1 :(得分:9)

这里有很多好的答案,但他们似乎都错过了一个重要的观点,我认为这是OP问题的主要内容,所以这里有。我说的是像C ++这样的编译语言,解释的语言要复杂得多。

编译程序时,编译器会检查您的代码以查找所有变量。一些变量将是全局的(或静态的),有些变量将是本地的。对于静态变量,它为它们分配固定的内存地址。这些地址可能是顺序的,并且它们从某个特定值开始。由于大多数体系结构(以及虚拟内存机制)上的内存分段,每个应用程序都可以(可能)使用相同的内存地址。因此,如果我们假设存储空间程序允许在我们的示例中使用从0开始,则编译的每个程序都将第一个全局变量放在位置0.如果该变量是4个字节,则下一个将位于位置4,这些不会与系统上运行的其他程序冲突,因为它们实际上是在运行时映射到内存的任意顺序部分。这就是为什么它可以在编译时分配一个固定的地址而不用担心遇到其他程序。

对于局部变量,它们被分配了一个相对于堆栈指针(通常是寄存器)的固定地址,而不是分配固定地址。当调用一个在堆栈上分配变量的函数时,堆栈指针只需移动所需的字节数,从而在堆栈的已用字节中创建一个间隙。所有局部变量都被赋予堆栈指针的固定偏移量,将它们放入该间隙。每次使用局部变量时,都会通过添加堆栈指针和偏移量来计算实际内存地址(忽略寄存器中的缓存值)。当函数返回时,堆栈指针被重置为调用函数之前的方式,因此包含局部变量的整个堆栈帧可以被下一个函数调用覆盖。

答案 2 :(得分:6)

读变量(编程) - 内存分配:
http://en.wikipedia.org/wiki/Variable_(programming)#Memory_allocation

这里是链接中的文本(如果你不想真正去那里,但你遗漏了文本中的所有链接):

  

变量分配的细节   以及他们价值观的表现   编程之间差异很大   语言和。的实现   给定的语言。很多语言   实现为。分配空间   局部变量,其范围持续   对于呼叫上的单个函数调用   堆栈,其内存是   自动回收的时候   功能返回。 (更一般地说,在   名称绑定,变量的名称   是一些地址的约束   特定块(连续序列)   内存中的字节数和操作数   变量操纵该块。   引用更常见   值大或为的变量   代码时未知的大小   编译。这些变量引用了   值的位置而不是   存储价值本身,这是   从称为的内存池分配   堆。

     

绑定变量具有值。一个值,   然而,这是一个抽象,一个想法;   在实现中,值是   由一些数据对象表示,其中   存储在计算机的某个地方   记忆。程序或运行时   环境,必须留出记忆   每个数据对象,因为内存是   有限的,确保这个记忆是   当对象出现时,可以重复使用   不再需要代表某些人   变量的值。

     

从堆分配的对象必须   被收回 - 特别是当   不再需要对象。在一个   垃圾收集语言(如   C#,Java和Lisp),运行时   环境自动回收   现存变量不能的对象   更长时间参考他们。在   非垃圾收集语言,例如   作为C,程序(和程序员)   必须明确分配内存,和   然后释放它,收回它   记忆。如果不这样做会导致   内存泄漏,堆在哪里   随着程序运行而耗尽,冒着风险   最终因疲惫而失败   可用内存。

     

当变量引用数据时   一些动态创建的结构   其组件可能只是间接的   通过变量访问。在这样的   情况,垃圾收集者(或   类似的程序功能   缺乏垃圾的语言   收藏家)必须处理案件   其中只有一部分内存   从变量需要到达   被收回

答案 3 :(得分:6)

有一个多步舞,将c = 5转换为机器指令以更新内存中的位置。

  1. 编译器分两部分生成代码。有指令部分(加载一个地址为C的寄存器;用文字5加载一个寄存器;存储)。并且有一个数据分配部分(对于一个称为“C”的变量,在偏移0处留下4个字节的房间)。

  2. “链接加载器”必须以操作系统能够运行它的方式将这些东西放入内存。加载程序请求内存,操作系统分配一些虚拟内存块。操作系统还通过一组不相关的管理机制将虚拟内存映射到物理内存。

  3. 加载程序将数据页放入一个位置,将指令部分放入另一个位置。请注意,指令使用相对地址(数据页的偏移量为0)。加载程序提供数据页面的实际位置,以便指令可以解析实际地址。

  4. 当执行实际的“store”指令时,操作系统必须查看引用的数据页是否实际位于物理内存中。它可能位于交换文件中,必须加载到物理内存中。正在使用的虚拟地址被转换为内存位置的物理地址。

答案 4 :(得分:4)

它内置于程序中。

基本上,当一个程序被编译成机器语言时,它就变成了一系列指令。一些指令内置了内存地址,这就是“链的末端”,可以这么说。编译器决定每个变量的位置,并将此信息刻录到可执行文件中。 (请记住,编译器是您正在编写的程序的一个不同的程序;只关注您自己的程序目前的工作方式。)

例如,

ADD [1A56], 15

可能会将位置1A56的值加15。 (该指令将使用处理器理解的一些代码进行编码,但我不会解释。)

现在,其他指令允许您使用“变量”内存地址 - 一个本身从某个位置加载的内存地址。这是C中指针的基础。你当然不能拥有这些指针的无限链,否则就会耗尽内存。

我希望能够解决问题。

答案 5 :(得分:3)

我将用非常基本的术语来表达我的回答。请不要被侮辱,我只是不确定你已经多么熟练,并希望提供一个可以作为初学者的人可以接受的答案。

你的假设实际上并不遥远。您运行代码的程序,通常称为编译器(或解释器,取决于语言),跟踪您使用的所有变量。您可以将变量视为一系列箱,并将各个数据保存在这些箱中。垃圾箱上有标签,当您将源代码构建到可以运行的程序中时,所有标签都会继续运行。编译器会为您处理这个问题,因此当您运行程序时,会从各自的bin中获取正确的内容。

您使用的变量只是另一层标签。这使您更容易跟踪。变量在内部存储的方式可能在它们上面有非常复杂或含糊的标签,但您需要担心的是在代码中如何引用它们。保持一致,使用良好的变量名称,并跟踪您对变量所做的事情,编译器/解释器负责处理与之相关的低级任务。这是一个非常简单的基本情况,可以使用内存进行变量。

答案 6 :(得分:1)

答案 7 :(得分:1)

简化为裸机,变量查找减少到一个地址,该地址是寄存器(堆栈指针)中保存的基本指针的某个静态已知偏移量,或者它是一个常量地址(全局变量)。

在解释语言中,一个寄存器通常被保留用于保存指向数据结构的指针(“环境”),该指针将变量名称与其当前值相关联。

答案 8 :(得分:1)

计算机最终只能进行打开和关闭 - 我们可以方便地将其抽象为二进制。这种语言是最基本的级别,称为机器语言。我不确定这是否是民间传说 - 但是一些程序员曾经(或者可能仍然)直接用机器语言编程。键入或读入二进制文件会非常麻烦,这就是为什么十六进制通常用于缩写实际二进制文件的原因。

因为我们大多数人都不是学者,所以机器语言被抽象为汇编语言。 Assemply是一种直接控制记忆的非常原始的语言。命令数量非常有限(push / pop / add / goto),但这些命令最终完成了所有编程。不同的机器架构有不同的组装版本,但要点是有几十个密钥存储器寄存器(物理上在CPU中) - 在x86架构中它们是EAX,EBX,ECX,EDX ......这些包含数据或CPU用来确定下一步做什么的指针。 CPU一次只能执行1项操作,并使用这些寄存器来确定下一步操作。计算机似乎能够同时做很多事情,因为CPU可以非常快速地处理这些指令 - (每秒数百万/十亿条指令)。当然,多核处理器使事情变得复杂,但是我们不要去那里......

因为我们大多数人都不聪明或不够精确,无法在程序集中编程,您可以轻松地使系统崩溃,所以汇编被进一步抽象为第3代语言(3GL) - 这是您的C / C ++ / C#/ Java等。 ..当您告诉其中一种语言将整数值5放在变量中时,您的指令存储在文本中;汇编程序将您的文本编译为汇编文件(可执行文件);当程序执行时,程序及其指令由CPU排队,当显示该特定代码行时,它将被读入CPU寄存器并进行处理。

关于这些语言的“不够聪明”的评论有点诙谐。从理论上讲,你越远离零和一个简单的人类语言,你就能更快,更有效地生成代码。

答案 9 :(得分:1)

这里有一个重要的缺陷,就是假设所有变量都存储在内存中。好吧,除非你将CPU寄存器计为内存,否则这将不完全正确。有些编译器会优化生成的代码,如果它们可以将变量保存在寄存器中,那么一些编译器就会使用它! 然后,当然,存在堆和堆栈内存的复杂问题。局部变量可以位于两者中!首选位置将位于堆栈中,该堆栈的访问方式比堆栈更频繁。几乎所有局部变量都是这种情况。全局变量通常是最终可执行文件的数据段的一部分,并且往往成为堆的一部分,尽管您无法释放这些全局内存区域。但是堆通常用于新内存块的动态分配,通过 alloc 为它们提供内存。

但是使用全局变量,代码将确切地知道它们的位置,从而在代码中写出它们的确切位置。 (好吧,它们从数据段开始的位置无论如何。)寄存器变量位于CPU中,编译器确切地知道哪个寄存器,也只是告诉代码。堆栈变量位于与当前堆栈指针的偏移量处。此堆栈指针将一直增加和减少,具体取决于调用其他过程的过程级别数。 只有堆值很复杂。当应用程序需要在堆上存储数据时,它需要第二个变量来存储它的地址,否则它可能会失去跟踪。第二个变量称为指针,位于全局数据或堆栈的一部分。 (或者,在极少数情况下,在CPU寄存器中。)

哦,它甚至比这更复杂,但是由于这些信息过度,我已经看到一些眼睛在滚动。 : - )

答案 10 :(得分:1)

将记忆想象成一个抽屉,根据你的自发需要决定如何将记忆分开。

当声明一个整数或任何其他类型的变量时,编译器或解释器(以较小者为准)在其数据段(汇编程序中的DS寄存器)中分配一个内存地址,并根据类型的长度保留一定数量的以下地址在位。

根据你的问题,一个整数是32位长,因此,从一个给定的地址开始,假设D003F8AC,该地址后面的32位将保留给你声明的整数。

在编译时,无论您引用变量,生成的汇编代码都会将其替换为DS地址。因此,当您获得变量C的值时,处理器将查询地址D003F8AC并检索它。

希望这会有所帮助,因为你已经有了很多答案。 : - )