我教了一门课程,学生可以在其中提出有关编程的问题(!):我得到了这个问题:
为什么机器选择变量进入内存?我们能告诉它在哪里存储变量吗?
我真的不知道该说什么。这是我的第一次尝试:
编译器(而不是机器)自动选择在进程地址空间中存储变量的位置。使用 C,我们不能告诉机器在哪里存储变量。
但是这种“自动”有点虎头蛇尾,并且引出了问题......我意识到我什至不知道它是编译器还是运行时或操作系统或谁来完成分配。也许有人能比我更好地回答学生的问题。
我教了一门课程,学生可以在其中提出有关编程的问题(!):我得到了这个问题:
为什么机器选择变量进入内存?我们能告诉它在哪里存储变量吗?
我真的不知道该说什么。这是我的第一次尝试:
编译器(而不是机器)自动选择在进程地址空间中存储变量的位置。使用 C,我们不能告诉机器在哪里存储变量。
但是这种“自动”有点虎头蛇尾,并且引出了问题......我意识到我什至不知道它是编译器还是运行时或操作系统或谁来完成分配。也许有人能比我更好地回答学生的问题。
这个问题的答案非常复杂,因为根据变量范围、大小和编程环境,内存分配有多种方法。
通常local variables
被放在“堆栈”上。这意味着编译器会为“堆栈指针”分配一个偏移量,该偏移量可能会根据当前函数的调用而有所不同。即编译器假定堆栈指针+4、堆栈指针+8 等内存位置可由程序访问和使用。从函数中提取后return
,不能保证内存位置保留这些值。
这被映射到类似于以下的汇编指令。esp
是堆栈指针,esp + N
指的是相对于 esp 的内存位置:
mov eax, DWORD PTR SS:[esp]
mov eax, DWORD PTR SS:[esp + 4]
mov eax, DWORD PTR SS:[esp + 8]
然后是堆分配的变量。这意味着有一个库调用从标准库(alloc
在 C 或new
C++ 中)请求内存。该内存一直保留到程序执行结束。alloc
并new
在称为堆的内存区域中返回指向内存的指针。分配函数必须确保不保留内存,这有时会使堆分配变慢。此外,如果您不想耗尽内存,您应该free
(或delete
) 不再使用的内存。堆分配在内部非常复杂,因为标准库必须跟踪内存中已使用和未使用的范围以及已释放的内存范围。因此,即使释放堆分配的变量也可能比分配它更耗时。有关更多信息,请参阅malloc() 如何在内部实现?
了解堆栈和堆之间的区别对于学习如何使用 C 和 C++ 编程非常重要。
人们可能会天真地假设,通过设置指向任意地址的指针,int *a = 0x123
应该可以寻址计算机内存中的任意位置。这并不完全正确,因为(取决于 CPU 和系统)程序在寻址内存时受到严格限制。
在有指导的课堂体验中,通过将源代码编译为汇编程序来探索一些简单的 C 代码可能是有益的(例如 gcc 可以做到这一点)。一个简单的函数int foo(int a, int b) { return a+b;}
就足够了(没有优化)。然后看到类似的东西int bar(int *a, int *b) { return (*a) + (*b);}
;
调用 bar 时,在堆栈上分配一次参数,每个 malloc 一次。
编译器确实执行了一些相对于基地址的变量放置和对齐,这些基地址由程序/标准库在运行时获得。
要深入了解与内存相关的问题,请参阅 Ulrich Drepper 的“每个程序员应该了解的关于内存的知识” http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.91.957
然后还有垃圾收集,它在许多脚本语言(Python、Perl、Javascript、lisp)和设备独立环境(Java、C#)中很流行。它与堆分配有关,但稍微复杂一些。
各种编程语言仅基于堆(无堆栈python)或完全基于堆栈(第四)。
我认为这个问题的答案始于对内存中程序布局的理解。在操作系统之下,计算机的主存储器只是一个巨大的阵列。当你运行一个程序时,操作系统会占用这块内存并将其分成逻辑部分,用于以下目的:
堆栈:此内存区域存储有关当前范围内的所有函数的信息,包括当前正在运行的函数及其所有祖先。存储的信息包括局部变量和函数完成时返回的地址。
堆:当您想要动态分配一些存储空间时使用此内存区域。通常,您的局部变量将在存储数据的堆中包含一个地址(即,它将是一个指针),并且您可以将此地址发布到程序的其他部分,而不必担心当前数据会被覆盖功能超出范围。
数据、bss、文本段:这些或多或少超出了这个特定问题的范围,但它们存储诸如全局数据和程序本身之类的东西。
希望有帮助。网上也有很多不错的资源。我刚刚搜索了“内存中程序的布局”并找到了这个: http ://duartes.org/gustavo/blog/post/anatomy-of-a-program-in-memory
我想补充几个。对于您知道内存映射和运行地址以及使用自己的链接器脚本编译源代码的固件 -
您可以使用 section 属性将自定义部分分配给变量,然后通过链接描述文件将特定地址分配给自定义部分。然后变量将获得一个固定/分配的地址。
函数内的局部变量通常会在函数的堆栈框架内按顺序排列。