【问题标题】:What does it means "All memory allocated on the stack is known at compile time"?这是什么意思“堆栈上分配的所有内存在编译时都是已知的”?
【发布时间】:2016-11-26 00:11:36
【问题描述】:

阅读this great tutorial 关于堆栈与堆的内容,我对这句话有一个疑问:堆栈上分配的所有内存在编译时都是已知的

我的意思是,如果我在依赖于用户输入的for 循环内(i 从 0 到 X),并且在 for 内,我会在堆栈上分配内存(例如创建一些类并放在一个类容器中),它不知道编译程序时堆栈如何增长(它错过了用户的输入)。

我是不是误会了什么?

【问题讨论】:

  • 您应该为您的假设场景添加代码。我认为@DevSolar 是对的,但如果您提供了您正在考虑的代码,它就不会那么模棱两可了。

标签: c++ compilation stack allocation


【解决方案1】:

在考虑的情况下,堆栈意味着编译器为您的方法中定义的局部变量分配的内存。 “你分配”(在引号中,因为它是为你完成的)这个内存在定义变量时:

void myMethod(int x)
{
    int y;
    for (y = 0; y < 10; y++)
    {
       int z = x + y;
    }
}

本例中的所有xyz都是分配在堆栈中的局部变量。

当您使用 new 运算符创建一些实例时,您会在堆中分配内存(此处您 ALLOCATE 不带引号),但要存储此分配内存的地址,您可能会使用在堆栈中分配的指针变量:

int * p = new int[10];

现在p 是局部变量(存储在堆栈中),它存储在堆中为 10 个整数组成的数组分配的内存地址。

当编译器解析您的源代码时,它会为调用您的方法制作一组指令(就特定处理器而言),此时它会计算所有局部变量所需的内存大小,并在程序执行时分配内存在方法开始之前(方法完成后,所有内存将被释放,即堆栈将被释放方法执行期间使用的数据“将丢失”)。

【讨论】:

    【解决方案2】:

    我的意思是,如果我在一个依赖于用户输入(i 从 0 到 X)的 for 循环中,并且在 for 中我在堆栈上分配内存(例如创建一些类的新实例并放入一个class-container),它不知道编译程序时堆栈如何增长(它错过了用户的输入)。

    所以你有一个类容器...

    std::vector< SomeClass > vec;
    

    ...在堆栈上。在循环中,您创建某个类的新实例...

    for ( size_t i = 0; i < X; ++i )
    {
        SomeClass x;
    

    ...在堆栈上。当您将其放入容器中时...

        vec.push_back( x );
    }
    

    ...该容器将实例存储在堆上。

    你只有 一个 SomeClass 在堆栈上,而这一事实在编译时是已知的。堆栈不会超过那个实例。

    虽然种方法可以在运行时增加堆栈(例如alloca()),所以本教程中的通用声明并不完全正确。

    【讨论】:

    • 但在每次迭代中,都会创建SomeClass x。如果我有 X=20,则在内存中创建 20 个不同的类。不是吗?
    • @paizza:每个实例都在其循环结束时被销毁(因为它超出了范围)。在第二次迭代中,您无法再从第一次迭代中访问 x。您存储在容器中的是x副本,它驻留在堆上。
    • @paizza:另外请注意,我只是在谈论 current 函数的堆栈 frame。查看 Smeeheey 的回答,了解关于 overall 堆栈内存使用情况的非常正确的陈述。
    • 现在确定SomeClass x; 是否进入堆栈。不等于SomeClass x = new Someclass?它进入堆......
    • @paizza:你必须写SomeClass * x = new SomeClass;——你会有一个指针(在堆栈上)指向一个实例(在堆上)。指针将在循环结束时超出范围,就像在循环内声明的任何其他堆栈对象一样;堆对象将持续存在。
    【解决方案3】:

    对于读者来说,所做的声明稍微简化了一点。您说得对,堆栈本质上是动态的,实际分配的数量可能会因动态输入而异。这是一个带有递归函数的简单示例:

    void f(int n)
    {
        int x = n * 10;
        if(x == 0) return;
    
        std::cout << x << std::endl;
        f(n - 1);
    }
    
    int main()
    {
        int n;
        std::cout << "Enter n: " << std::endl;
        std::cin >> n;
        f(n);
    }
    

    这里显然f(一个递归函数)的调用次数取决于用户输入的n,因此对于任何给定的实例化,编译器不可能知道局部变量x的确切内存地址在f。然而,它所做 知道的是x 与本地堆栈帧 的偏移量,我相信这就是示例所指的内容。堆栈帧是每次发生函数调用时准备的堆栈的局部区域。在给定的堆栈帧内,局部变量的位置实际上是相对于堆栈帧开始的已知常量偏移量。这个“开始”在每次调用中都保存在标准寄存器中,因此编译器要找到任何本地的地址所要做的就是将其固定的已知偏移量应用于这个动态的“基指针”。

    【讨论】:

    • 所以在编译时已知的是每个stack frame 的大小。但不会有多少堆栈帧。你是说这个吗?
    • 正是如此。在特定堆栈帧中查找变量的格式为BP - x,其中BP 是表示该帧的“基指针”的寄存器(在编译时未知),x 是编译时有问题的变量的常量(例如BP - 8)。 - 符号在那里是因为堆栈向下增长,因此远离基指针
    猜你喜欢
    • 2021-10-23
    • 1970-01-01
    • 2015-05-14
    • 2011-05-09
    • 1970-01-01
    • 2023-03-09
    • 2014-11-02
    相关资源
    最近更新 更多