程序的机器指令在运行时存储在哪里?

8
据我所知,每当我们运行任何程序时,程序的机器指令都会加载在RAM中。此外,内存有两个区域:堆栈和堆。
我的问题是:机器指令存储在哪个内存区域中?堆栈还是堆?
我了解到,尽管函数内没有声明变量,但以下程序会导致运行时错误。这背后的原因是堆栈溢出。那么我是否可以假定该函数的机器指令存储在堆栈中?
int func()
    {
            return func();
    }

1
这取决于操作系统,但文本段通常既不是堆栈也不是堆。 - Basile Starynkevitch
1
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Lundin
1
@SouravGhosh,实际上我是新来的。以后我会尽量避免这种情况。 - Muktadir Rahman
1
@MuktadirRahman 没问题。同时注意格式,干杯!! - Sourav Ghosh
1
@MuktadirRahman:嗯,那个运行时错误/堆栈溢出就是当你遇到一个不终止的递归时会出现的问题。;-) - DevSolar
显示剩余6条评论
7个回答

15

因为它不像堆和栈一样是动态分配的,所以它不属于动态分配。

可执行文件(.text)和它包含的任何静态数据,例如全局变量的初始值(.data/.rodata),将被装载到未使用的RAM区域。然后,设置可执行文件请求的任何零初始化内存(.bss)。

只有在这之后,才会设置main()的栈。如果您进入另一个函数,则在栈上分配堆栈内存,保留返回地址、函数参数和任何本地声明的变量以及通过alloca()分配的任何内存[1]。当您从该函数返回时,释放内存。

堆内存由malloc()calloc()realloc()分配。当您使用free()realloc()释放内存时,堆内存将被释放。

用于可执行文件及其静态数据的RAM直到进程终止才会被释放。

因此,栈和堆基本上受应用程序控制。可执行文件本身的内存受可执行文件加载器/操作系统控制。在适当配置的操作系统中,您甚至无法写入该内存。


关于您编辑的问题,答案是否定的(风格不佳,编辑一个问题给它一个完全不同的角度)。

可执行代码仍然留在加载它的位置。调用函数不会将机器指令放置在堆栈上。您的func()(一个不带参数的函数)放置在堆栈上的唯一内容是返回地址,一个指针,它指示在当前函数返回后执行应该继续进行的位置。

由于没有任何调用返回,您的程序会一直在堆栈上添加返回地址,直到无法再添加为止。这与机器代码指令毫无关系。


[1]: 注意,这些并不是C语言标准的一部分,而是实现定义的内容,可能会有所不同——我展示的是一个简化版的情况。例如,函数参数可能会被传递到CPU寄存器中,而不是在堆栈上。


2
@MuktadirRahman:函数的返回并不重要,但是仍然会在堆栈中添加一个返回地址。这两个东西是不同的。--我刚意识到我在写返回地址是什么时犯了一个大脑错误。它当然不是指向堆栈的指针,而是指向可执行代码的指针。已经相应地编辑了答案。(少喝咖啡,多思考。:-D) - DevSolar
1
正如我所说,这是实现定义的,因为标准根本没有提到“堆栈”。在使用堆栈的地方(诚然,几乎无处不在),它不会在编译时设置,因为编译器无法知道您的调用堆栈可能有多深。一些系统可以动态增长堆栈,其他系统在启动时静态分配堆栈...这是一个复杂的话题,远远超出了SO评论的范围。 - DevSolar
1
具有内存虚拟化的系统(例如Windows和Linux)过去会将可执行文件加载到最低内存,并将初始堆栈分配在最高内存(就在操作系统本身下方)。堆分配会从可执行文件之后向上增长,而堆栈则会向下增长,直到内存不足。没有虚拟化的系统(例如经典的AmigaOS)通常会分配大小为X的堆栈,这通常是可配置的。如果用完了,那么就用完了。{耸肩}但据我所知,在任何情况下都不会在编译时确定堆栈大小。 - DevSolar
1
这种情况可能出现在非常受限制的嵌入式环境中,其中没有操作系统和可配置堆栈大小的加载器。现代系统今天会“随机化”它们的内存空间,因此事物不再驻留在可预测(虚拟)地址上。这使得恶意软件更难,但对于那些试图解释内存布局的人来说也更难了。无论如何,您永远不应该真正考虑这一点,因为当您开始利用这些细节时,您已经离开了行为良好的符合编程的领域... - DevSolar
1
@starriet 不完全是这样的。该过程并没有获取整个虚拟内存范围。这样做效率低下,因为映射所有这些页面会消耗(物理)内存。该进程在低地址处获取足够的内存以容纳可执行文件及其静态数据(暂时忽略内存空间随机化),以及高地址处的“起始堆栈”。中间的范围未映射。如果用户空间内存管理(malloc)需要更多堆内存,则会向操作系统发出系统调用,操作系统会向上扩展堆。如果堆栈已满,则操作系统会检测到并向下扩展堆栈。 - DevSolar
显示剩余11条评论

4

既非前者也非后者。

你的程序镜像包含了代码和静态数据(例如所有字符串常量、静态数组、结构等),它们将被加载到内存的不同段中。

堆和栈是动态的数据结构,用于存储数据,它们将在程序启动时创建。栈是硬件支持的解决方案,而堆是标准库支持的解决方案。

因此,你的代码将位于代码段,静态数据和堆将位于数据段,而栈将位于栈段。


3

程序的机器指令被加载到RAM中。

对于托管的"类PC"系统而言,这是正确的。在嵌入式系统上,代码通常直接从闪存中执行。

同样,存在两个内存区域:堆和栈。

不,这是过于简化的表述,许多糟糕的编程教师都会这样教。除此之外,还有很多其他区域:所有具有静态存储的变量所在的.data.bss区域,常量所在的.rodata区域等等。

存储程序代码的段通常称为.text


1
除了堆栈和堆之外,还有多个内存段。以下是程序在内存中的布局示例:
              +------------------------+
high address  | Command line arguments |   
              | and environment vars   |  
              +------------------------+
              |         stack          |
              | - - - - - - - - - - -  |
              |           |            |
              |           V            |
              |                        |
              |           ^            |
              |           |            |
              | - - - - - - - - - - -  |
              |          heap          |
              +------------------------+
              |    global and read-    |
              |       only data        |
              +------------------------+
              |     program text       |
 low address  |    (machine code)      |
              +------------------------+   

不同平台的细节会有所不同,但这种布局在基于x86的系统中非常普遍。机器代码占据自己的内存段(在ELF格式中标记为.text),全局只读数据将存储在另一个段中(.rdata.rodata),未初始化的全局变量在另一个段中(.bss)等。一些段是只读的,一些是可写的。


1

既非堆栈也非堆。

通常,可执行指令位于代码段中。

引用维基百科文章

在计算机中,代码段(也称为文本段或简称为文本)是对象文件的一部分或程序虚拟地址空间的相应部分,其中包含可执行指令。

当加载器将程序放入内存以便执行时,将分配各种内存区域(特别是页)

运行时,对象文件的代码段被加载到内存中对应的代码段中。特别地,它与堆栈无关。


编辑:

在您上面的代码片段中,您遇到的是所谓的无限递归

即使您的函数不需要堆栈中的任何本地变量空间,它仍然需要推入外部函数的返回地址,然后调用内部函数,从而声明堆栈空间,但永远不会返回 [弹出地址以腾出堆栈空间] [就像在不可回溯的点一样],从而导致堆栈溢出。


1
再次强调,内存分为两个区域:栈和堆。
实际上,在主流操作系统上通常会有更多内容:
- 每个运行线程都有一个栈; - 可以根据需要分配多个堆(实际上,就内存管理器而言,您要求在虚拟地址空间中“启用”一些内存页面,“堆”是从通常使用某种堆管理器代码来有效地在分配之间分配这些内存部分的事实派生出来的); - 可能会有内存映射文件和共享内存; - 最重要的是,可执行文件(以及动态库)被映射到进程的内存中,通常将代码区域(所谓的“文本”段)映射为只读模式,并且其他区域(通常与初始化全局和静态变量以及由加载器固定的内容有关)采用写时复制方式。
因此,代码存储在可执行文件的相关部分中,该文件被映射到内存中。

1
他们通常在一个名为.text的部分。
在Linux上,您可以使用core-utils中的size命令列出ELF对象或可执行文件的各个部分。例如,在tst ELF可执行文件上:
$ size -Ax tst | grep "^\.text"
.text                0x1e8   0x4003b0
$

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接