关于堆栈增长和寻址的困惑

Confusion about stack growth and addressing

我正在尝试更好地理解堆栈中的项目以及它们的寻址方式。我找到的文章 here 似乎表明,当初始化 MIPS 堆栈时,会分配固定数量的内存,并且堆栈会向下增长到堆栈限制,这似乎是更小的地址。我会假设基于这个逻辑当遍历0x0000时会发生堆栈溢出?

我知道 MIPS 是大端,但这会改变堆栈的增长方式吗?我写了我认为可以在 x86_64 机器上快速观察这一点的方法,但堆栈似乎在增长,正如我最初假设的那样。

#include <iostream>
#include <vector>
int main() {
    std::vector<int*> v;
    for( int i = 0; i < 10; i++ ) {
        v.push_back(new int);
        std::cout << v.back() << std::endl;
    }
}

我也对并非所有内存地址都不是连续的这一事实感到困惑,这让我觉得我做了一些愚蠢的事情。有人可以澄清一下吗?

x86 机器上的栈也是向下增长的。字节顺序与堆栈增长的方向无关。

一台机器的堆栈与std::vector<>完全没有关系。此外,new int 分配堆内存,因此它绝对不会告诉您有关堆栈的任何信息。

为了查看堆栈的增长方向,您需要执行以下操作:

recursive( 5 );

void recursive( int n )
{
    if( n == 0 )
        return;
    int a;
    printf( "%p\n", &a );
    recursive( n - 1 );
}

(请注意,如果您的编译器足够聪明,可以优化尾递归,那么您需要告诉它不要优化它,否则观察结果将全部错误。)

本质上,您在编程中使用了 3 种类型的内存:静态、dynamic/heap 和堆栈。

静态内存由编译器预先分配,由程序中静态声明的常量和变量组成。

Heap是你可以自由分配和释放的内存

堆栈是为函数中声明的所有局部变量分配的内存。这很重要,因为每次调用该函数时,都会为其变量分配新的内存。因此,每次调用函数都将确保它拥有自己唯一的变量副本。每次你从函数 return 中释放内存。

只要遵循上述规则,堆栈如何管理就完全没有关系。然而,让程序内存分配在较低地址 space 并向上增长,而堆栈从顶部内存 space 开始并向下增长是很方便的。大多数系统都实现了这个方案。

一般有一个栈指针register/variable指向当前栈地址。当一个函数被调用时,它会将这个地址减少它的变量所需的字节数。当它调用下一个函数时,这个新函数将从调用者已经减少的新指针开始。当函数 returns 它恢复它开始的指针。

可能有不同的方案,但据我所知,mips 和 i86 遵循这一方案。

而且程序中本质上只有一个虚拟内存space。这取决于操作系统 and/or 编译器如何使用它。编译器将在逻辑区域中拆分内存供自己使用,并希望根据平台文档中定义的调用约定来处理它们。

因此,在我们的示例中,vi 分配在函数堆栈上。 cout 是静态的。每个 new int 在堆中分配 space。 v 不是一个简单的变量,而是一个包含管理列表所需字段的结构。所有这些内部结构都需要 space。因此,每个 push_back 都会以某种方式修改这些字段以指向分配的 'int'。 push_back() 和 back() 是函数调用,并为内部变量分配自己的堆栈,以免干扰 top 函数。