2017-07-22 63 views
0

我想更好地理解栈上的项目以及如何解决它们。我发现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; 
    } 
} 

我还感到困惑,并不是所有的内存地址的的不出现是连续的,这让我觉得我做了一些愚蠢的。有人可以澄清吗?

+3

该程序在堆上分配数据,而不是在堆栈上分配数据。 – interjay

+0

哦,是因为我使用矢量这个事实吗?会使用字符数组在堆栈上? – mreff555

+2

矢量的数据和来自'new int'的分配都在堆上。只有本地变量本身(不包括任何内部分配的数据,例如向量的)在堆栈中。 – interjay

回答

1

本质上有3种类型的内存用于编程:静态,动态/堆栈和堆栈。

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

堆是,你可以自由地分配和释放

堆栈内存是其获取在函数声明的所有局部变量分配的内存。这很重要,因为每次调用函数时都会为其变量分配一个新的内存。所以,每次调用函数都会确保它有自己独特的变量副本。每次从函数返回时,内存都被释放。

只要遵循上述规则,堆栈的管理方式绝对没有关系。然而,将程序存储器分配到较低的地址空间并成长是很方便的,并且堆栈要从最高的存储器空间开始并长大。大多数系统实施这个方案。

通常有一个堆栈指针寄存器/变量指向当前堆栈地址。当一个函数被调用时,它会通过它的变量需要的字节数来减少这个地址。当它调用下一个函数时,这个新函数将从调用者已经减少的新指针开始。当函数返回时,它恢复它从其开始的指针。

可能会有不同的计划,但据我所知,mips和i86会跟随这个计划。

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

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

2

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

机器的堆栈与std::vector<>完全无关。此外,new int分配堆内存,所以它告诉你绝对没有关于堆栈。

为了在堆栈增长的方向看,你需要做的是这样的:

recursive(5); 

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

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

+0

x86_64整数我假设在我的例子中地址的差异应该是32位或0x20,但是当我编译和运行程序时,大部分地址是32位,但不是全部。 – mreff555

+1

@ mreff555:这是因为堆栈中还有其他东西,例如函数的返回地址。堆栈通常是对齐的,所以它可能需要额外的填充。而且,在这里忘记一下:)当我们谈论内存地址之间的差异时,我们用字节计算。 – geza

+0

这是有道理的,但我被告知我写的是在堆上。无论如何,我想这是有道理的。我只是认为载体是连续的。我只用了一点点,因为我只是在脑海中做了一个快速转换。 – mreff555