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

时间:2009-01-03 05:41:58

标签: c++ static garbage-collection stack heap

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

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

你可以自己操作内存,你不能使用这个垃圾收集器吗?

有人告诉我这个宣言:

int * asafe=new int;

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

asafe=new int;

9 个答案:

答案 0 :(得分:205)

答案 1 :(得分:52)

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

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


自动存储时间

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

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并未绑定到其类的特定对象,而是绑定到类本身。上面所有三个名称的地址是相同的,都表示相同的对象。有关静态对象何时以及如何初始化的特殊规则,但现在不要关注它。这意味着术语静态初始化顺序惨败


动态存储时间

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

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参考,请参阅提升文档:http://www.boost.org/doc/libs/1_37_0/libs/smart_ptr/shared_ptr.htm

答案 2 :(得分:37)

精心设计,就像“简短回答”一样:

  • 静态变量(类)
    lifetime =程序运行时间(1)
    visibility =由访问修饰符(private / protected / public)确定

  • 静态变量(全局范围)
    lifetime =程序运行时间(1)
    visibility =在(2)

  • 中实例化的编译单元
  • 堆变量
    life =由您定义(new to delete)
    visibility =您定义的(无论您指定指针的是什么)

  • 堆栈变量
    visibility =从声明到退出范围
    lifetime =从声明直到声明范围退出


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

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

答案 3 :(得分:5)

我确信其中一位学生很快会得到一个更好的答案,但主要区别在于速度和体型。

堆栈

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

分配速度极慢。但你有GB可以玩,并指向。

垃圾收集器

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

答案 4 :(得分:3)

  

静态和堆栈有什么问题?

“静态”分配的问题是分配是在编译时进行的:你不能用它来分配一些可变数量的数据,其数量在运行时才知道。

在“堆栈”上分配的问题是,只要执行分配的子例程返回,分配就会被销毁。

  

我可以编写整个应用程序而不在堆中分配变量吗?

也许但不是一个非平凡的,正常的,大的应用程序(但是所谓的“嵌入式”程序可能在没有堆的情况下使用C ++的子集编写)。

  

什么垃圾收集器呢?

它会一直监视您的数据(“标记和扫描”),以检测您的应用程序何时不再引用它。这对应用程序来说很方便,因为应用程序不需要释放数据......但垃圾收集器的计算成本可能很高。

垃圾收集器不是C ++编程的常用功能。

  

你可以自己操作内存,你不能使用这个垃圾收集器吗?

了解确定性内存释放的C ++机制:

  • '静态':永不解除分配
  • 'stack':变量“超出范围”
  • 'heap':指针被删除时(由应用程序显式删除,或在某些或其他子例程中隐式删除)

答案 5 :(得分:1)

当堆栈太“深”并且溢出可用于堆栈分配的内存时,堆栈内存分配(函数变量,局部变量)可能会出现问题。堆用于需要从多个线程或整个程序生命周期中访问的对象。您可以在不使用堆的情况下编写整个程序。

如果没有垃圾收集器,您可以非常轻松地泄漏内存,但您也可以指定何时释放对象和内存。我在运行GC时遇到了Java问题并且我有一个实时进程,因为GC是一个独占线程(没有别的东西可以运行)。因此,如果性能至关重要并且您可以保证没有泄漏的对象,则不使用GC非常有帮助。否则,当你的应用程序消耗内存并且你必须追踪泄漏源时,它只会让你讨厌生命。

答案 6 :(得分:1)

如果您的程序不知道要分配多少内存(因此您不能使用堆栈变量),该怎么办?说链接列表,列表可以增长,而不知道它的大小是什么。因此,当您不知道将多少元素插入其中时,在堆上进行分配对于链表是有意义的。

答案 7 :(得分:1)

在某些情况下GC的一个优点是其他人的烦恼;对GC的依赖鼓励人们不要多考虑它。理论上,等到“空闲”期间或直到绝对必须,它会窃取带宽并导致应用程序中的响应延迟。

但你不必“不考虑它”。就像多线程应用程序中的其他所有内容一样,当您可以屈服时,您可以屈服。例如,在.Net中,可以请求GC;通过这样做,而不是更频繁地运行GC,可以更频繁地缩短运行GC,并分散与此开销相关的延迟。

但这打败了GC的主要吸引力,似乎“鼓励他们不必多思考它,因为它是自动化的。”

如果您在GC变得普遍并且对malloc / free和new / delete感到满意之前第一次接触到编程,那么甚至可能会发现GC有点烦人和/或不信任(因为可能是不信任'优化',这已经有一段曲折的历史。)许多应用程序容忍随机延迟。但对于那些没有随机延迟不太可接受的应用程序,一个常见的反应就是避开GC环境并朝着完全不受管理的代码(或上帝禁止,长期垂死的艺术,汇编语言)的方向前进。

我有一个暑假学生,一个实习生,聪明的孩子,他在GC上断奶;他非常赞赏GC的优势,即使在非托管C / C ++编程时,他拒绝遵循malloc / free new / delete模型,因为引用“你不应该用现代编程语言来做这件事。”而且你知道?对于小型,短期运行的应用程序,您确实可以逃脱,但不适用于长期运行的高性能应用程序。

答案 8 :(得分:0)

堆栈是由编译器分配的内存,当我们编译程序时,默认编译器从OS分配一些内存(我们可以从IDE中的编译器设置更改设置)而OS是给你内存的那个,它取决于系统上的许多可用内存和许多其他东西,并且当我们声明它们复制的变量(ref as formals)时,将分配到堆栈内存,这些变量被推送到堆栈它们遵循一些命名约定默认情况下它的CDECL在Visual工作室 中文:中缀符号: C = A + B; 堆栈推送从右到左推送,b到堆栈,操作员,堆栈和那些i的结果,即堆叠。 在预修补符号中: = +驾驶室 这里所有变量都被推到第一个堆栈(从右到左),然后进行操作。  编译器分配的内存是固定的。因此,假设我们的应用程序分配了1MB的内存,假设变量使用了700kb的内存(除非动态分配,否则所有局部变量都被推送到堆栈中),因此剩余的324kb内存被分配给堆。 并且这个堆栈的生命周期较短,当函数的范围结束时,这些堆栈被清除。