堆栈:低内存与高内存地址的实际位置?

5 c stack

我在一些教程中阅读了有关堆栈和内存地址位置的内容,并想知道为什么它们对低内存位置和高内存位置的引用不同?

这很令人困惑。

例如

低内存地址位于顶部,高内存地址位于底部 在此输入图像描述

低内存地址位于底部,高内存地址位于顶部 在此输入图像描述

当我用一个简单的程序尝试它时C,似乎低内存地址位于顶部。从bfbf7498 > bfbf749c > bfbf74a0 > bfbf74a4 > bfbf74a8 > bfbf74ac

user@linux:~$ cat > stack.c                     
#include <stdio.h>

int main()
{
        int A = 3;
        int B = 5;
        int C = 8;
        int D = 10;
        int E = 11;
        int F;
        F = B + D;

        printf("+-----------+-----+-----+-----+\n");
        printf("| Address   | Var | Dec | Hex |\n");
        printf("|-----------+-----+-----+-----|\n");
        printf("| %x  | F         | %d  | %X   |\n",&F,F,F);
        printf("| %x  | E         | %d  | %X   |\n",&E,E,E);
        printf("| %x  | D         | %d  | %X   |\n",&D,D,D);
        printf("| %x  | C         | 0%d  | %X   |\n",&C,C,C);
        printf("| %x  | B         | 0%d  | %X   |\n",&B,B,B);
        printf("| %x  | A         | 0%d  | %X   |\n",&A,A,A);
        printf("+-----------+-----+-----+-----+\n");
}
user@linux:~$ 

user@linux:~$ gcc -g stack.c -o stack ; ./stack 
+-----------+-----+-----+-----+
| Address   | Var | Dec | Hex |
|-----------+-----+-----+-----|
| bfbf7498  | F   | 15  | F   |
| bfbf749c  | E   | 11  | B   |
| bfbf74a0  | D   | 10  | A   |
| bfbf74a4  | C   | 08  | 8   |
| bfbf74a8  | B   | 05  | 5   |
| bfbf74ac  | A   | 03  | 3   |
+-----------+-----+-----+-----+
user@linux:~$ 
Run Code Online (Sandbox Code Playgroud)

小智 1

为什么堆栈地址会朝着内存地址递减的方向增长?

这个线程对你的问题有一个很好的答案。它还具有非常好的视觉效果。

https://unix.stackexchange.com/questions/4929/what-are-high-memory-and-low-memory-on-linux?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa

这也是一个很好的解释(但具体与 unix/linux 相关)

本质上它完全依赖于平台

为什么某些堆栈内存增长不同的解释:

根据操作系统(Linux 实时与普通)和下面的语言运行时系统,可以使用多种不同的方法:

1) 动态的,通过页面错误

通常会预先分配一些实际页面到更高的地址,并将初始 sp 分配给该地址。栈向下增长,堆向上增长。如果页面错误发生在堆栈底部下方,则会分配并映射丢失的中间页面。有效地自动从顶部到底部增加堆栈。通常存在执行此类自动分配的最大值,该最大值可以或不能在环境 (ulimit)、exe 标头中指定,或由程序通过系统调用 (rlimit) 动态调整。尤其是这种可调整性在不同操作系统之间差异很大。通常还存在一个限制,即距离堆栈底部“多远”,页面错误被认为是正常的并且会发生自动增长。请注意,并非所有系统的堆栈都向下增长:在 HPUX 下,它(使用过?)向上增长,因此我不确定 PA-Risc 上的 linux 会做什么(有人可以对此发表评论)。

2)固定尺寸

其他操作系统(尤其是在嵌入式和移动环境中)要么根据定义具有固定大小,要么在 exe 标头中指定,或者在创建程序/线程时指定。特别是在嵌入式实时控制器中,这通常是一个配置参数,并且各个控制任务会获得修复堆栈(以避免失控线程占用更高优先级控制任务的内存)。当然,在这种情况下,内存也可能仅被虚拟分配,直到真正需要为止。

3) 按页、意大利面条和类似的

这种机制往往会被遗忘,但仍在某些运行时系统中使用(我知道 Lisp/Scheme 和 Smalltalk 系统)。它们根据需要动态分配和增加堆栈。但是,不是作为单个连续段,而是作为多页块的链接链。它需要编译器生成不同的函数入口/出口代码,以便处理段边界。因此,此类方案通常由语言支持系统而不是操作系统本身来实现(过去很早 - 叹息)。原因是,当您在交互式环境中拥有许多(例如数千个)线程时,预分配例如 1Mb 只会填满您的虚拟地址空间,并且您无法支持以前未知单个线程的线程需求的系统(这是通常是在动态环境中的情况,其中用户可能会将评估代码输入到单独的工作区中)。因此,上面方案 1 中的动态分配是不可能的,因为会有其他线程拥有自己的堆栈。堆栈由较小的段(例如 8-64k)组成,这些段从池中分配和释放,并链接到堆栈段链中。对于延续、协程等的高性能支持也可能需要这样的方案。

现代 unixes/linuxes 和(我猜,但不是 100% 确定)windows 使用方案 1) 作为 exe 的主线程,2) 用于额外的 (p-) 线程,这些线程需要线程创建者给出的固定堆栈大小最初。大多数嵌入式系统和控制器使用固定(但可配置)的预分配(在许多情况下甚至是物理预分配)。

抱歉,如果这个答案有点密集,我不确定是否可以简单地给出一个有效的解释。至于为什么你在C语言中给出的例子的低内存地址位于顶部,最简单的解释是C语言就是这样构建的。