C/ c++与Java的垃圾回收

Garbage Collection in C/C++ versus Java

本文关键字:Java c++      更新时间:2023-10-16

C/c++中没有自动垃圾回收。

假设我用C/c++写了一个简单的程序,并创建了一个对象。假设只有10个或极其有限的地址可供分配。

我有一个运行了100次的for循环,其中每次循环运行时都会创建这个对象。

在Java中,由于有自动垃圾收集,在执行单个循环后,每次都会自动删除对象的地址。

粗糙的例子:

for(int i = 0; i < 100; i++)
{
   Object o = new Object;
}

在C/c++中,我们必须手动删除for循环中的Object地址。我们是否必须每次都重新启动才能正确地删除c++中的Object引用?


对于那些说在c++中多次删除对象没有问题的人。从维基百科:

对象被删除超过一次,或者当程序员尝试时释放指向对象的指针从免费存储中分配,动态系统的灾难性故障内存管理系统可以产生。这些行动的结果可以包括堆损坏,过早破坏不同的(和新创建的)对象,它恰好占据在内存中的位置与将删除的对象和其他对象相乘未定义行为的形式。

链接:Manual Memory Management

所以有风险?

c++没有(内置的)垃圾收集,但这并不意味着您应该让动态分配的内存永远保持分配状态。您可以并且应该释放它。最基本的方法是,当你不再需要对象引用(或指针,因为这是c++)时,手动释放它:

for(int i = 0; i < 100; i++)
{
   // Dynamically allocate an object
   Object* o = new Object();
   // do something with object
   // Release object memory (and call object destructor, if there is one)
   delete o;
}

如果你在堆栈上分配对象,当它超出作用域时,它总是自动被释放,你不必等待垃圾收集发生——它总是立即被释放:

for(int i = 0; i < 100; i++)
{
   // Create a new object on the stack
   Object o = Object(); // Note we're not using the new keyword here.
   // Do something with object
   // Object gets automatically deallocated, or more accurately, in this specific
   // case (a loop), the compiler will optimize things, so object's destructor
   // will get called, but the object's stack memory will be reused.
}

c++堆栈值的这种行为(当它们超出作用域时自动销毁),它被笨拙地称为RAII(资源获取即初始化),允许Java不能做的非常好的事情。其中之一是智能指针,它允许动态分配的对象自动释放,就像它们的堆栈对应对象一样,如果你愿意,你可以使用复杂的智能指针来实现你自己的垃圾收集版本。

RAII的另一个优点是在c++中很少需要finally-block:引用应该立即释放的资源的局部变量通常在堆栈上分配,因此会自动释放。这里不需要finally块。

实际上,当用c++编程时,你通常会把局部变量放在堆栈上(并且无需动动手指就能获得RAII的所有优点),除非你需要让它们存活更长时间(例如,你需要创建一个对象并存储对它的引用,当你离开创建该对象的函数时,它仍然存活)。在这种情况下,您可以直接使用指针,但如果您不想处理手动删除指针以及它可能导致的所有问题,您通常会使用智能指针。智能指针是在堆栈上分配的对象,它包装了一个"哑"(即普通)指针,并在调用其析构函数时删除指针。更高级的智能指针版本可以实现引用计数(因此,只有当所有引用它的智能指针超出作用域时,指向对象才会被释放),甚至可以实现垃圾收集。标准库附带了两个智能指针:auto_ptrsmart_ptr(后者是引用计数,但一些旧的编译器可能不支持它)。

Java不会在每次循环结束时自动删除对象。相反,它会等待,直到有很多垃圾,然后通过并收集它。Java不保证收集对象需要多长时间。

在c++中,一旦程序退出,将返回所有资源。您不需要重新启动(假设有一个合理的操作系统)来确保资源被返回。你的操作系统会确保你的程序没有释放的资源在它关闭时被释放。

如果你在c++中删除了这个对象,那么它马上就消失了。你不需要做任何事情来恢复内存

c++当然有自动存储时间;它只是有其他类型的存储时间,以及选择最合适的能力。

在类似您的示例中,您将使用自动存储:

for (int i = 0; i < 100; ++i) {
    Object o;
    // The object is automatically destroyed after each iteration
}

如果对象需要在循环之外生存,可能是因为它被传递给另一个对象来管理,那么您将使用智能指针(最接近于Java引用的c++等价物)。以下是使用auto_ptrshared_ptr的示例:

// some function that takes ownership of an object
void register(auto_ptr<Object> const & o);
void register(shared_ptr<Object> const & o);
for (int i = 0; i < 100; ++i) {
    auto_ptr<Object> o(new Object);
    register(o); // ownership may be transferred; our pointer is now null in that case
    // The pointer is automatically destroyed after each iteration, 
    // deleting the object if it still owns it.
}
for (int i = 0; i < 100; ++i) {
    shared_ptr<Object> o(new Object);
    register(o); // ownership is shared; our pointer is still valid
    // The pointer is automatically destroyed after each iteration,
    // deleting the object if there are no other shared pointers to it.
}

在这些情况下都不需要手动删除对象;这只有在处理原始对象指针时才有必要,在我看来,只有在绝对必要的时候才应该这样做。

c++在这方面也比Java有一个优势:销毁总是确定性的(也就是说,您确切地知道何时发生)。在Java中,一旦对象被丢弃,您不知道垃圾收集器何时(甚至是否)将删除它。这意味着,如果对象管理的资源(如锁或数据库连接)在使用后需要释放,则由对象的用户手动释放它。在c++中,这可以在析构函数中自动完成,使类更容易使用,更不容易出错。

C/c++中没有自动垃圾回收

假。C和c++没有强制要求自动垃圾收集,但它是可用的(例如,参见Boehm/Demers/Weiser收集器)。

在C/c++中,我们必须手动删除for循环中的Object地址。

,假的。在C或c++中,我们使用自动存储类定义对象,这将确定性地销毁对象:

for(int i = 0; i < 100; i++)
{
   Object o;
}
例如,让我们做一个快速的测试,通过像这样定义Object:
struct Object { 
    Object() { std::cout << "Created an Objectn"; }
    ~Object() { std::cout << "Destroyed an Objectn"; }
};
对于上面的循环,这会生成:
Created an Object
Destroyed an Object
Created an Object
Destroyed an Object
Created an Object
Destroyed an Object

[删除97个相同模式的重复]

我们是否必须每次都重新启动才能正确地删除c++中的Object引用?

不,当然不是。c++和Java在这方面的根本区别在于,在c++中,对象销毁是确定的,而在Java中则不是。例如,在c++中,上面的代码必须完全遵循规定的模式——循环体是一个块,对象必须在进入块时创建,在退出块时销毁。在Java中,您无法获得这样的保证。它可能只在分配了10或20个对象后才销毁第一个对象。对象可以按任意顺序销毁,并且不能保证任何特定对象将被销毁。

这种差异并不总是重要的,但肯定是重要的。在c++中,它被用来支持RAII(又名。SBRM——堆栈绑定资源管理)。这不仅可以确保在不再需要时释放内存(即Java的自动垃圾收集器处理相同的事情),还可以确保其他资源(从文件到小部件到网络或数据库连接)也被自动处理。

如果您有短寿命的对象,并且性能很关键(大多数时候不是),您可以创建一个在每个循环中重用的可变对象。这将工作从每次迭代变为每次循环。

List list = new ArrayList(); // mutable object.
for(int i = 0; i < 100; i++) {
   list.clear();
   // do something with the list.
}
// one list is freed later.

您可以使列表成为成员字段(意味着可变对象可能永远不会被释放),如果您的类不需要是线程安全的,这是可以的。

假设您使用的是典型的操作系统(Windows/Linux) -不,您不需要重新启动。操作系统将通过进程/虚拟内存结构保护你。

只有你的进程会耗尽内存。当你的进程结束时,操作系统会在你之后进行清理。

在许多没有操作系统的小型嵌入式系统上运行-是的,你会崩溃或锁定处理器并需要重新启动。

加上已经给出的答案,你总是可以编写信号处理程序,在接收到它时,你可以清理进程使用的内存。

通常情况下,当您退出操作系统时,操作系统会释放您的程序。但有些操作系统可能不会(我在http://www.beck-ipc.com/en/products/sc1x/sc13.asp上看到过:RTOS没有在退出时释放内存,所以我在几次启动后没有释放所有对象,所以我不得不重新启动)。但是大多数操作系统会清除/释放以前使用过的程序(linux和windows会),所以你不必担心。