Stack,Static和Heap in C ++

我已经搜索过,但我对这三个概念并没有很好的理解。 我何时必须使用动态分配(在堆中)以及它的真正优势是什么? 什么是静态和堆栈的问题? 我可以在没有在堆中分配变量的情况下编写整个应用程序吗?

我听说其他语言包含“垃圾收集器”,因此您不必担心内存。 垃圾回收器做什么?

你可以自己操纵内存来处理你无法使用这个垃圾回收器的功能吗?

有人对我说,有了这个声明:

int * asafe=new int;

我有一个“指针指针”。 这是什么意思? 它不同于:

asafe=new int;


一个类似的问题被问到,但它没有询问静力学问题。

什么是静态,堆和堆栈内存的总结:

  • 一个静态变量基本上是一个全局变量,即使你不能全局访问它。 通常在可执行文件中有一个地址。 整个程序只有一个副本。 无论你进入函数调用(或类)的次数(以及多少个线程!),变量都指向相同的内存位置。

  • 堆是可以动态使用的一堆内存。 如果你想要一个4kb的对象,那么动态分配器会查看它在堆中的可用空间列表,挑出一个4kb的块,然后提供给你。 一般来说,动态内存分配器(malloc,new等等)从内存结束开始并向后工作。

  • 解释堆栈如何增长和缩小有点超出这个答案的范围,但只要说你总是只从最后添加和删除就足够了。 堆栈通常从高处开始向下延伸到较低的地址。 当堆栈满足中间某处的动态分配器时(但是指物理内存和虚拟内存以及碎片),内存用完。 多个线程将需要多个堆栈(过程通常会为堆栈保留最小大小)。

  • 当你想要使用每一个:

  • 统计信息/全局信息对于您知道永远需要的内存非常有用,并且您知道您永远不想解除分配。 (顺便说一下,嵌入式环境可能被认为只有静态内存......堆栈和堆是第三种内存类型共享的已知地址空间的一部分:程序代码。程序通常会动态分配它们静态内存,当他们需要像链表这样的东西时,但不管怎样,静态内存本身(缓冲区)本身并不是“分配”的,而是其他对象从缓冲区保存的内存中分配出来用于这个目的。在非嵌入式系统中,控制台游戏会频繁地避开内置的动态内存机制,有利于通过为所有分配使用预设大小的缓冲区来严格控制分配过程。)

  • 当你知道只要函数在范围内(在栈上),堆栈变量很有用,你会希望变量保持不变。 对于代码所在位置需要的变量,堆栈很适合,但在代码之外不需要这些变量。 当你访问一个资源时,它们也非常好,比如一个文件,并且希望当你离开代码时资源会自动消失。

  • 当您希望比以上更灵活时,堆分配(动态分配的内存)非常有用。 通常会调用一个函数来响应事件(用户单击“创建框”按钮)。 正确的响应可能需要分配一个新的对象(一个新的Box对象),该对象应该在函数退出后长时间存在,所以它不能在堆栈中。 但是你不知道在节目开始时你会想要多少盒子,所以它不能是静态的。

  • 垃圾收集

    最近我听说垃圾收集器有多好,所以也许有点不同意的声音会有帮助。

    当性能不是一个大问题时,垃圾收集是一个很好的机制。 我听说GC正在变得越来越好,但事实是,您可能会被迫接受性能损失(取决于使用情况)。 如果你很懒,它仍然可能无法正常工作。 在最好的时候,垃圾收集器意识到当你意识到没有更多的引用时,你的记忆会消失(参见引用计数)。 但是,如果您有一个引用自身的对象(可能通过引用另一个引用的对象),则引用计数本身并不表示可以删除内存。 在这种情况下,GC需要查看整个参考汤并确定是否有任何岛屿只能由他们自己引用。 不管怎样,我想这是一个O(n ^ 2)操作,但不管它是什么,如果你对性能感到担心,它可能会变得糟糕。 (编辑:Martin B指出合理有效的算法是O(n),如果你关心性能,那么它仍然是O(n),并且可以在没有垃圾回收的情况下在不变的情况下释放。

    就我个人而言,当我听到人们说C ++没有垃圾收集功能时,我的头脑将这个标签标记为C ++的一个功能,但我可能只是少数。 人们在C和C ++编程中学习最困难的事情可能是指针以及如何正确处理其动态内存分配。 其他一些语言,比如Python,如果没有GC,会很糟糕,所以我认为这取决于你想要的语言。 如果你想要可靠的性能,那么没有垃圾收集的C ++是我能想到的Fortran的这一面。 如果你想要易用性和训练轮(为了避免崩溃而不要求你学习“正确”的内存管理),用GC选择一些东西。 即使你知道如何很好地管理内存,它也会节省你花时间优化其他代码的时间。 再也没有太多的性能损失了,但是如果你真的需要可靠的性能(以及知道到底发生了什么的能力,那么在封面下),那么我会坚持使用C ++。 有一个原因,我听说过的每个主要游戏引擎都是用C ++编写的(如果不是C或汇编)。 Python等对脚本编程很好,但不是主要的游戏引擎。


    以下当然都不太精确。 当你读它时,用一粒盐把它拿走:)

    那么,你提到的三件事是自动的,静态的和动态的存储时间 ,这与物体的存在时间以及它们何时开始生命有关。


    自动存储时间

    短期数据使用自动存储持续时间,仅在某些块内本地需要:

    if(some condition) {
        int a[3]; // array a has automatic storage duration
        fill_it(a);
        print_it(a);
    }
    

    一旦我们退出块,寿命就会结束,并在对象被定义后立即启动。 它们是最简单的存储时间,并且比特定的动态存储时间更快。


    静态存储时间

    对自由变量使用静态存储持续时间,如果它们的作用域允许这样的用法(命名空间作用域),并且对于需要通过它们的作用域(本地作用域)退出而延长它们的生命周期的本地变量,它可以随时由任何代码访问用于需要由其类的所有对象(类范围)共享的成员变量。 它们的生命周期取决于它们所处的范围。它们可以具有名称空间范围本地范围以及类范围 。 他们两个人的真实情况是,一旦他们的生活开始了,他们的一生就会在节目结束时结束 。 这里有两个例子:

    // static storage duration. in global namespace scope
    string globalA; 
    int main() {
        foo();
        foo();
    }
    
    void foo() {
        // static storage duration. in local scope
        static string localA;
        localA += "ab"
        cout << localA;
    }
    

    该程序打印ababab ,因为localA在其块的退出时不会被销毁。 当控制达到定义时 ,可以说具有本地范围的对象开始生命周期。 对于localA ,当进入函数的主体时发生。 对于命名空间范围内的对象,生命周期从程序启动开始。 对于类范围的静态对象也是如此:

    class A {
        static string classScopeA;
    };
    
    string A::classScopeA;
    
    A a, b; &a.classScopeA == &b.classScopeA == &A::classScopeA;
    

    如你所见, classScopeA并没有绑定到它的类的特定对象,而是绑定到类本身。 上面所有三个名字的地址是一样的,都表示同一个对象。 关于什么时候以及如何初始化静态对象有特殊的规则,但现在我们不关心这一点。 这意味着静态初始化顺序失败。


    动态存储时间

    最后的存储时间是动态的。 如果你想让对象存在于另一个岛上,并且你想要引用指向它们的指针,就可以使用它。 如果对象很大 ,并且如果要创建仅在运行时已知的大小数组,则还可以使用它们。 由于这种灵活性,具有动态存储持续时间的对象是复杂且管理缓慢的。 具有该动态持续时间的对象在发生适当的新操作符调用时开始生命周期:

    int main() {
        // the object that s points to has dynamic storage 
        // duration
        string *s = new string;
        // pass a pointer pointing to the object around. 
        // the object itself isn't touched
        foo(s);
        delete s;
    }
    
    void foo(string *s) {
        cout << s->size();
    }
    

    只有当你为他们调用delete时,它的生命周期才会结束 如果你忘记了,那些对象永远不会结束生命。 定义用户声明构造函数的类对象不会调用它们的析构函数。 具有动态存储持续时间的对象需要手动处理其生存期和相关的内存资源。 存在的图书馆可以简化它们的使用。 通过使用智能指针可以建立特定对象的 显式垃圾回收

    int main() {
        shared_ptr<string> s(new string);
        foo(s);
    }
    
    void foo(shared_ptr<string> s) {
        cout << s->size();
    }
    

    你不必关心调用delete:如果引用该对象的最后一个指针超出了作用域,共享ptr会为你做这件事。 共享的ptr本身具有自动存储持续时间。 所以它的生命周期是自动管理的,允许它检查是否应该在其析构函数中删除指向动态对象的指针。 有关shared_ptr参考,请参阅boost文档:http://www.boost.org/doc/libs/1_37_0/libs/smart_ptr/shared_ptr.htm


    有人精心说过,就像“简答”一样:

  • 静态变量(类)
    生命周期=程序运行时(1)
    可见性=由访问修饰符(私有/保护/公共)确定

  • 静态变量(全局范围)
    生命周期=程序运行时(1)
    visibility =它在(2)中实例化的编译单元

  • 堆变量
    生命周期=由您定义(新的删除)
    可见性=由你定义(无论你分配指针)

  • 堆栈变量
    可见性=从声明到范围退出
    生命周期=从声明到声明范围退出


  • (1)更确切地说:从初始化直到编译单元取消初始化(即C / C ++文件)。 编译单元的初始化顺序不是由标准定义的。

    (2)注意:如果你在一个头文件中实例化一个静态变量,每个编译单元都会得到它自己的副本。

    链接地址: http://www.djcxy.com/p/241.html

    上一篇: Stack, Static, and Heap in C++

    下一篇: What is the difference between new/delete and malloc/free?