当前位置:  开发笔记 > 编程语言 > 正文

C++中的堆栈,静态和堆

如何解决《C++中的堆栈,静态和堆》经验,为你挑选了4个好方法。

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

我听说其他语言包含了"垃圾收集器",所以你不必担心内存.垃圾收集器做什么?

您可以自己操作内存,而不能使用此垃圾收集器吗?

有人告诉我这个声明:

int * asafe=new int;

我有一个"指针指针".这是什么意思?它不同于:

asafe=new int;



1> markets..:

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

静态,堆和堆栈内存的摘要:

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

堆是一堆可以动态使用的内存.如果你想要4kb的对象,那么动态分配器将查看堆中的可用空间列表,选择一个4kb的块,并将其提供给你.通常,动态内存分配器(malloc,new等)从内存结束开始并向后工作.

解释堆栈如何增长和缩小有点超出了这个答案的范围,但足以说你总是只添加和删除.堆栈通常从高处开始并向下扩展到较低的地址.当堆栈遇到中间某处的动态分配器时,内存不足(但请参考物理内存与虚拟内存和碎片).多个线程将需要多个堆栈(该过程通常为堆栈保留最小大小).

当你想要使用每一个时:

静态/全局变量对于你知道你将永远需要的内存非常有用,并且你知道你不想要释放.(顺便说一句,嵌入式环境可能被认为只有静态内存......堆栈和堆是第三种内存类型共享的已知地址空间的一部分:程序代码.程序通常会动态分配它们的静态内存,当他们需要像链接列表这样的东西.但无论如何,静态内存本身(缓冲区)本身并不是"已分配",而是为了这个目的而将其他对象分配给缓冲区所占用的内存.你可以这样做在非嵌入式游戏中,控制台游戏将经常避开内置的动态内存机制,有利于通过使用预设大小的缓冲区来严格控制分配过程.)

当你知道只要函数在范围内(在某个堆栈上)时,堆栈变量就很有用,你会希望保留变量.堆栈适用于它们所在的代码所需的变量,但在该代码之外不需要.当您访问资源(如文件)时,它们也非常适合您,并希望在您离开该代码时资源自动消失.

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

垃圾收集

我最近听说过垃圾收集器有多棒,所以也许有点不同意见会有所帮助.

当性能不是一个大问题时,垃圾收集是一种很好的机制.我听说GC正在变得更好,更复杂,但事实是,你可能会被迫接受性能损失(取决于用例).如果你很懒,它仍然可能无法正常工作.在最好的时候,垃圾收集器意识到当你意识到没有更多的引用时你的记忆会消失(参见引用计数)).但是,如果你有一个引用自身的对象(可能通过引用另一个引用的对象),那么单独引用计数并不表示可以删除内存.在这种情况下,GC需要查看整个参考汤,并确定是否有任何岛屿仅由他们自己引用.另外,我猜这是一个O(n ^ 2)操作,但不管它是什么,如果你完全关心性能,它会变坏.(编辑:Martin B 指出,对于合理有效的算法,它是O(n).如果你关注性能,那么它仍然是O(n)太多,并且可以在没有垃圾收集的情况下在恒定时间内解除分配.)

就我个人而言,当我听到人们说C++没有垃圾收集时,我的脑海中将其标记为C++的一个特征,但我可能只是少数.人们学习C和C++编程最困难的可能是指针以及如何正确处理动态内存分配.如果没有GC,其他一些语言(如Python)会很糟糕,所以我认为这取决于你想要的语言.如果你想要可靠的性能,那么没有垃圾收集的C++是我能想到的Fortran这一方面唯一的东西.如果您想要易于使用和训练轮(为了避免您在不需要学习"正确"内存管理的情况下崩溃),请使用GC选择一些内容.即使您知道如何妥善管理内存,也可以节省您优化其他代码的时间.实际上没有太多的性能损失了,但是如果你真的需要可靠的性能(并且能够准确地知道发生了什么,何时,在幕后)那么我会坚持使用C++.有一个原因,我听说过的每个主要游戏引擎都是C++(如果不是C或汇编).Python等人可以很好地编写脚本,但不是主要的游戏引擎.


现在垃圾收集通常比手动释放内存更好,因为它发生在几乎没有工作要做的时候,而不是释放可以在其他情况下使用性能时发生的内存.
你对垃圾收集的贬损处理有点没用.
只是一个小评论 - 垃圾收集没有O(n ^ 2)的复杂性(实际上,这对于性能来说确实是灾难性的).一个垃圾收集周期所花费的时间与堆的大小成正比 - 请参阅http://www.hpl.hp.com/personal/Hans_Boehm/gc/complexity.html.

2> Johannes Sch..:

以下当然都不太准确.当你读它时带上一粒盐:)

嗯,你提到的三件事是自动,静态和动态存储持续时间,这与物体生存时间和生命开始时间有关.


自动存储持续时间

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

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 s(new string);
    foo(s);
}

void foo(shared_ptr s) {
    cout << s->size();
}

您不必关心调用delete:如果引用该对象的最后一个指针超出范围,则共享ptr会为您执行此操作.共享ptr本身具有自动存储持续时间.因此它的生命周期是自动管理的,允许它检查是否应该删除其析构函数中指向动态对象的内容.有关shared_ptr参考,请参阅增强文档:http://www.boost.org/doc/libs/1_37_0/libs/smart_ptr/shared_ptr.htm



3> peterchen..:

精心设计,就像"简短回答":

static variable(class)
lifetime = program runtime(1)
visibility =由访问修饰符确定(private/protected/public)

static variable(全局范围)
lifetime =程序运行时(1)
visibility =在(2)中实例化的编译单元

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

stack variable
visibility =从声明到scope退出
生命周期=从声明直到声明范围退出


(1)更确切地说:从初始化到编译单元的去初始化(即C/C++文件).标准未定义编译单元的初始化顺序.

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



4> Chris Smith..:

我敢肯定其中一个学生会很快得到一个更好的答案,但主要区别在于速度和体型.

分配速度更快.它在O(1)中完成,因为它是在设置堆栈帧时分配的,因此它基本上是免费的.缺点是,如果你的堆栈空间不足,你就会被剔除.你可以调整堆栈大小,但IIRC你可以玩大约2MB.此外,一旦退出该功能,堆栈上的所有内容都将被清除.因此,稍后引用它可能会有问题.(指向堆叠已分配对象的指针会导致错误.)

分配速度极慢.但你有GB玩,并指向.

垃圾收集器

垃圾收集器是一些在后台运行并释放内存的代码.当你在堆上分配内存时,很容易忘记释放它,这被称为内存泄漏.随着时间的推移,应用程序消耗的内存会增长并增长,直到崩溃为止.让垃圾收集器定期释放你不再需要的内存有助于消除这类错误.当然,这需要付出代价,因为垃圾收集器减慢了速度.

推荐阅读
小白也坚强_177
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有