87

These days, so many languages are garbage collected. It is even available for C++ by third parties. But C++ has RAII and smart pointers. So what's the point of using garbage collection? Is it doing something extra?

And in other languages like C#, if all the references are treated as smart pointers(keeping RAII aside), by specification and by implementation, will there still be any need of garbage collectors? If no, then why is this not so?

4
  • 1
    One thing I understood after asking this question- Smart pointers needs RAII to manage the automatic deallocation.
    – Gulshan
    Dec 27, 2010 at 12:45
  • 9
    Smart pointers mean using RAII for GC ;)
    – Dario
    Dec 27, 2010 at 13:06
  • Heh, c# should have an option for handling all "garbage collection" with RAII. Circular references can be detected on application shutdown, all we need is see which allocations are still in memory after Program.cs-class has been deallocated. Then circular references can be replaced with some kind of week references.
    – AareP
    Feb 24, 2011 at 6:15
  • 5

11 Answers 11

83

So, what's the point of using garbage collection?

I'm assuming you mean reference counted smart pointers and I'll note that they are a (rudimentary) form of garbage collection so I'll answer the question "what are the advantages of other forms of garbage collection over reference counted smart pointers" instead.

  • Accuracy. Reference counting alone leaks cycles so reference counted smart pointers will leak memory in general unless other techniques are added to catch cycles. Once those techniques are added, reference counting's benefit of simplicity has vanished. Also, note that scope-based reference counting and tracing GCs collect values at different times, sometimes reference counting collects earlier and sometimes tracing GCs collect earlier.

  • Throughput. Smart pointers are one of the least efficient forms of garbage collection, particularly in the context of multi-threaded applications when reference counts are bumped atomically. There are advanced reference counting techniques designed to alleviate this but tracing GCs are still the algorithm of choice in production environments.

  • Latency. Typical smart pointer implementations allow destructors to avalanche, resulting in unbounded pause times. Other forms of garbage collection are much more incremental and can even be real time, e.g. Baker's treadmill.

32
  • 31
    Can't believe this answer ended up top answer. It shows a total lack of understanding of C++ smart pointers and makes claims that are so much out of sync with reality that its simply ridiculous. First, the smart pointer that in a well designed piece of C++ code will be most dominant is the unique pointer, not the shares pointer.en.cppreference.com/w/cpp/memory/unique_ptr And secondly, I can't believe you actually claiming 'performance' advantages and real-time advantages of non deterministic garbage collection over smart pointers. Jan 26, 2016 at 9:46
  • 16
    @user1703394: "I can't believe you actually claiming 'performance' advantages and real-time advantages of non deterministic garbage collection over smart pointers". The fact that garbage collection is much faster than scope-based reference counting is well known, has been widely documented in GC research papers over the past 55 years, is explained in every introductory text book on the subject (e.g. the GC Handbook by Jones et al.) and I have personally measured and documented this fact: flyingfrogblog.blogspot.co.uk/2011/01/…
    – J D
    Jan 26, 2016 at 20:27
  • 8
    These are all straw man arguments and only valid if you either completely ignore the actual question or ignore actual usage patterns of the different types of smart pointers. The question was about smart pointers. Yes shared_ptr is a smart pointer and yes shared_ptr is the most expensive of smart pointers, but no, there is no actual argument for their pervasive use anywhere close to making any performance argument close to relevant. Seriously, this answer should be moved to a question on reference counting. I'ts a poor reference counting answer to a good smart pointer question. Feb 9, 2016 at 15:02
  • 8
    @user1703394: "You have no idea". These ad-hominem attacks are pointless. If there is a reason why you think anything you have referred to is even relevant to this discussion please state it specifically. I agree that Rust is cleaner and safer than C++ and provides a new perspective but that has nothing whatsoever to do with this discussion. Unbounded pauses due to destructors avalanching will be just as prevalent in Rust as they are in C++. Move semantics address a completely unrelated problem. Here is the infinite loop in Rust: doc.rust-lang.org/stable/nomicon/destructors.html
    – J D
    Feb 10, 2016 at 12:38
  • 10
    @user1703394: "Unbounded pauses due to destructors is an unfortunate property of RAII being used for non-memory resources". No, this has nothing whatsoever to do with non-memory resources.
    – J D
    Feb 11, 2016 at 10:28
75

Since no one has looked at it from this angle, I'll rephrase your question: why put something into the language if you can do it in a library? Ignoring specific implementation and syntactic details, GC/smart pointers is basically a special case of that question. Why define a garbage collector in the language itself if you can implement it in a library?

There are a couple of answers to that question. The most important first:

  1. You ensure that all code can use it to interoperate. This is, I think, the big reason why code reuse and code sharing didn't really take off until Java/C#/Python/Ruby. Libraries need to communicate, and the only reliable shared language they have is what's in the language spec itself (and, to a degree, its standard library). If you've ever tried to reuse libraries in C++, you've likely experienced the horrendous pain that no standard memory semantics causes. I want to pass a struct to some lib. Do I pass a reference? Pointer? scoped_ptr? smart_ptr? Am I passing ownership, or not? Is there a way to indicate that? What if the lib needs to allocate? Do I have to give it an allocator? By not making memory management part of the language, C++ forces each pair of libraries to have to negotiate their own specific strategy here, and it's really hard to get them all to agree. GC makes that a complete non-issue.

  2. You can design the syntax around it. Because C++ doesn't encapsulate memory-management itself, it has to provide a range of syntactic hooks to let user-level code express all of the details. You have pointers, references, const, dereferencing operators, indirection operators, address-of, etc. If you roll memory management into the language itself, the syntax can be designed around that. All of those operators disappear and the language gets cleaner and simpler.

  3. You get a high return on investment. The value any given piece of code generates is multiplied by the number of people using it. This means that the more users you have, the more you can afford to spend on a piece of software. When you move a feature into the language, all users of the language will be using it. This means you can allocate more effort to it than you could to a library only used by a subset of those users. This is why languages like Java and C# have absolutely first-rate VMs and fantastically high-quality garbage collectors: the cost of developing them is amortized across millions of users.

4
  • Fantastic answer! If only I could upvote more than once... Dec 28, 2010 at 1:10
  • 10
    It's worth noting that garbage collection is not actually implemented in the C# language itself, but in the .NET Framework, specifically the Common Language Runtime (CLR). Jan 4, 2011 at 2:37
  • 9
    @RobertHarvey: It's not implemented by the language, but it wouldn't work without the cooperation of the language. For example, the compiler must include information specifying, at every point in the code, the location of every register or stack-frame offset that holds a reference to an unpinned object. That is an absolute no-exceptions-whatsoever invariant, which could not be upheld without language support.
    – supercat
    Dec 24, 2013 at 20:14
  • 1
    A major advantage of having GC support the language and required framework is that it ensures that no references will ever exist to memory that might be allocated for some other purpose. If one calls Dispose on an object which encapsulates a bitmap, any reference to that object will be a reference to a disposed bitmap object. If the object was deleted prematurely while other code still expects to use it, the bitmap class can ensure that the other code will fail in predictable fashion. By contrast, using a reference to freed memory is Undefined Behavior.
    – supercat
    Dec 24, 2013 at 20:18
37

Garbage collection basically just means that your allocated objects are automatically released at some point after they're not reachable any more.

More accurately, they're released when they become unreachable for the program, as circularly referenced objects would never get released otherwise.

Smart pointers just refer to any structure that behaves like an ordinary pointer but has some extra functionality attached. These include but are not limited to deallocation, but also copy-on-write, bound checks, ...

Now, as you have stated, smart pointers can be used to implement a form of garbage collection.

But the train of thought goes the following way:

  1. Garbage collection is a cool thing to have, as it's convenient and I have to take care of fewer things
  2. Therefore: I want garbage collection in my language
  3. Now, how can get GC into my language?

Of course, you can design it like this from start. C# was designed to be garbage collected, so just new your object and it'll be released when the references fall out of scope. How this is done is up to the compiler.

But in C++, there was no garbage collection intended. If we allocate some pointer int* p = new int; and it falls out of scope, p itself is removed from the stack, but nobody takes care of the allocated memory.

Now the only thing you have from start are deterministic destructors. When an object leaves the scope it has been created in, its destructor is called. In combination with templates and operator overloading, you can design a wrapper object that behaves like a pointer, but uses destructor functionality to clean up resources attached to it (RAII). You call this one a smart pointer.

This is all highly C++ specific: Operator overloading, templates, destructors, ... In this particular language situation, you have developed smart pointers to provide you with the GC you want.

But if you design a language with GC from start, this is merely an implementation detail. You just say object will be cleaned up and the compiler will do this for you.

Smart pointers like in C++ wouldn't probably be even possible in languages like C#'s, which have no deterministic destruction at all (C# works around this by providing syntactic sugar for calling a .Dispose() on certain objects). Unreferenced resources will finally be reclaimed by the GC, but it undefined when exactly this will happen.

And this, in turn, can allow the GC to do its work more efficient. Being built in deeper into the language than smart pointers, which are set on top of it, the .NET GC can e.g. delay memory operations and perform them in blocks to make them cheaper or even move memory around for increasing efficiency based on how often objects are accessed.

8
  • C# does have a form of deterministic destruction via IDisposable and using. But it requires a little bit of programmer effort, which is why it's usually only used for very scarce resources such as database connection handles. Dec 27, 2010 at 16:14
  • 7
    @JSBangs: Exactly. Just as C++ builds smart pointers around RAII to get GC, C# goes the other way and builds "smart disposers" around the GC to get RAII ;) In fact, it's a shame that RAII is so difficult in C# as it's great for exception-safe resource handling. F# for example tries a simpler IDisposable syntax by just replacing conventional let ident = value by use ident = value ...
    – Dario
    Dec 27, 2010 at 16:24
  • @Dario: "C# goes the other way and builds 'smart disposers' around the GC to get RAII". RAII in C# with using has nothing to do with garbage collection at all, it just calls a function when a variable falls out of scope just like destructors in C++.
    – J D
    Dec 27, 2010 at 17:53
  • 1
    @Jon Harrop: Please what? The statement you cite is about plain C++ destructors, without any reference counting/smart pointers/garbage collection involved.
    – Dario
    Dec 27, 2010 at 19:45
  • 2
    "Garbage collection basically just means that your allocated objects are automatically released when they're not being referenced any more. More accurately, they're released when they become unreachable for the program, as circularly referenced objects would never get released otherwise." ... More accurate would be to say they are automatically released at some point after, not when. Note that when implies that the reclamation happens immediately, when in fact reclamation often happens way later. Jun 8, 2015 at 20:33
7

There are two big differences, to my mind, between garbage collection and smart pointers as used for memory management:

  1. Smart pointers can't collect cyclic garbage; garbage collection can
  2. Smart pointers do all the work at the moments of referencing, dereferencing, and deallocation, on the application thread; garbage collection need not

The former means that GC will collect garbage that smart pointers won't; if you're using smart pointers, you have to avoid creating this kind of garbage, or be prepared to deal with it manually.

The latter means that no matter how smart smart pointers are, their operation will slow down the working threads in your program. Garbage collection can defer work, and move it to other threads; that lets it be more efficient overall (indeed, the runtime cost of a modern GC is less than a normal malloc/free system, even without the extra overhead of smart pointers), and do what work it still needs to do without getting in the way of the application threads.

Now, note that smart pointers, being programmatic constructs, can be used to do all sorts of other interesting things - see Dario's answer - which are completely outside the scope of garbage collection. If you want to do those, you will need smart pointers.

However, for the purposes of memory management, i don't see any prospect of smart pointers replacing garbage collection. They simply aren't as good at it.

27
  • 9
    @Tom: have a look at Dario’s answer for details about smart pointers. As for advantages of smart pointers – deterministic deallocation can be an enormous advantage when used to control resources (not only memory). In fact, this has proven so important that Microsoft has introduced the using block in subsequent versions of C#. Furthermore, the nondeterministic behaviour of GCs can be forbidding in real-time systems (which is why GCs aren’t used there). Also, let’s not forget that GCs are so complex to get right that most actually leak memory and are quite inefficient (e.g. Boehm …). Dec 27, 2010 at 13:26
  • 6
    The nondeterminism of GCs is, i think, a bit of a red herring - there are GC systems which are suitable for realtime use (like IBM's Recycler), even though the ones you see in desktop and server VMs aren't. Plus, using smart pointers means using malloc/free, and conventional implementations of malloc are nondeterministic due to the need to search the free list. Moving GC systems have more deterministic allocation times than malloc/free systems, although of course less deterministic deallocation times. Dec 27, 2010 at 13:49
  • 3
    As for complexity: yes, GCs are complex, but i'm not aware that "most actually leak memory and are quite inefficient", and would be interested to see some evidence otherwise. Boehm is not evidence, because it's a very primitive implementation, and it's built to serve a language, C, where accurate GC is fundamentally impossible due to the lack of type safety. It's a brave effort, and that it works at all is very impressive, but you can't take it as an exemplar of GC. Dec 27, 2010 at 13:50
  • 8
    @Jon: decidedly not bullshit. bugzilla.novell.com/show_bug.cgi?id=621899 or, more generally: flyingfrogblog.blogspot.com/2009/01/… This is well-known and a property of all conservative GCs. Dec 27, 2010 at 14:22
  • 4
    "The runtime cost of a modern GC is less than a normal malloc/free system." Red herring here. This is only because traditional malloc is a horribly inefficient algorithm. Modern allocators that use multiple buckets for different block sizes are much faster to allocate, far less prone to heap fragmentation, and still give you fast deallocation. Dec 27, 2010 at 17:25
5

The term garbage collection implies there is any garbage to collect. In C++ smart pointers come in multiple flavors, most importantly the unique_ptr. The unique_ptr is basically a single ownership and scoping construct. In a well designed piece of code most heap allocated stuff would normally reside behind unique_ptr smart pointers and ownership of those resources will be well defined at all times. There is hardly any overhead in unique_ptr and unique_ptr takes away most of the manual memory management problems that traditionally drove people to managed languages. Now that more cores running concurrently are becoming more common good, the design principles that drive code to use unique and well defined ownership at any point in time becomes more important for performance. The use of the actor model of computation allows for the construction of programs with a minimum amount of shared state between threads, and unique ownership plays a major role in making high performance systems make efficient use of many cores without the overhead of shared-between-threads data and the implied mutex requirements.

Even in a well designed program, especially in multi threaded environments, not everything can be expressed without shared data structures, and for those data structures that truly require, threads need to communicate. RAII in c++ works pretty well for lifetime concerns in a single threaded setup, in a multi threaded setup the lifetime of objects may not be completely stack hierarchically defined. For these situations, the use of shared_ptr offers a big part of the solution. You create shared ownership of a resource and this in C++ is the only place we see garbage, but at such small quantities that a proper designed c++ program should be considered more to implement 'litter' collection with shared-ptr's than full fledged garbage collection as implemented in other languages. C++ simply does not have that much 'garbage' to collect.

As stated by others, reference counted smart pointers are one form of garbage collection, and a for that has one major issue. The example that is used mostly as drawback of reference counted forms of garbage collection is the problem with the creation of orphaned data structures connected with smart pointers to each other that create object clusters that keep each other from being collected. While in a program designed according to the actor-model of computation, the data structures not usually allow for such noncollectable clusters to arise in C++, when you use the broad shared data approach to multi threaded programming, as is used predominantly in a large part of the industry, these orphaned clusters can quickly become a reality.

So to sum it all up, if by shared pointer usage you mean the wide use of unique_ptr combined with the actor model of computation approach for multi threaded programming and the limited use of shared_ptr, than other forms of garbage collection don't buy you any added benefits. If however a shared everything approach would have you end up with shared_ptr all over the place, than you should consider either switching concurrency models or switching to a managed language that is more geared towards the wider sharing of ownership and concurrent access to data structures.

2
  • 1
    Does it mean Rust does not need garbage collection?
    – Gulshan
    Jan 26, 2016 at 13:11
  • 1
    @Gulshan Rust is one of the very few languages that supports safe unique pointers. Jan 26, 2016 at 15:56
4

Most smart pointers are implemented using reference counting. That is, each smart pointer that refers to an object increments the objects reference count. When that count goes to zero, the object is released.

The problem there is if you have circular references. That is, A has a reference to B, B has a reference to C and C has a reference to A. If you're using smart pointers, then in order to free the memory associated with A, B & C you need to manually get in there an "break" the circular reference (e.g. using weak_ptr in C++).

Garbage collection (typically) works quite differently. Most garbage collectors these days use a reachability test. That is, it looks at all of the references on the stack and the ones that are globally accessible and then traces every object that those references refer to, and objects they refer to, etc. Everything else is garbage.

In that way, circular references don't matter any more - as long as neither A, B and C are reachable, the memory can be reclaimed.

There are other advantages to "real" garbage collection. For example, memory allocation is extremely cheap: just increment the pointer to the "end" of the memory block. Deallocation has a constant amortized cost as well. But of course languages like C++ allow you to implement memory management pretty much any way you like, so you could come up with an allocation strategy that's even faster.

Of course, in C++ the amount of heap-allocated memory is typically less than a reference-heavy language like C#/.NET. But that's not really a garbage-collection vs. smart pointers issue.

In any case, the issue isn't cut-and-dry one is better than the other. They each have advantages and disadvantages.

3

Garbage collection can be more efficient - it basically 'batches up' the overhead of the memory management and does it all at once. In general this will result in less overall CPU being expended on memory de-allocation, but it means that you'll have a big burst of de-allocation activity at some point. If the GC isn't properly designed this can become visible to the user as a 'pause' while the GC tries to de-allocate memory. Most modern GCs are very good at keeping this invisible to the user except under the most adverse conditions.

Smart pointers (or any reference counting scheme) have the advantage that they happen exactly when you'd expect from looking at the code (smart pointer goes out of scope, thing gets deleted). You get little bursts of de-allocation here and there. You overall may use more CPU time on de-allocation, but since it's spread out over all of the things happening in your program, it's less likely (baring de-allocate of some monster data structure) to become visible to your user.

If you are doing something where responsiveness matters, I would suggest that smart pointers/ref counting let you know exactly when things are happening, so you can know while coding what's likely to become visible to your users. In a GC setting you have only the most ephemeral of control over the garbage collector and simply have to try to work around the thing.

On the other hand, if overall throughput is your goal, a GC based system may be a much better choice, as it minimizes the resources needed to do memory management.

Cycles: I do not consider the problem of cycles to be a significant one. In a system where you have smart pointers, you tend toward data structures that don't have cycles, or you are simply careful about how you let go of such things. If necessary, keeper objects that know how to break the cycles in the owned objects can be used in order to automatically insure proper destruction. In some realms of programming this may be important, but for most day-to-day work, it's irrelevant.

1
2

It's about performance. Unallocating memory requires lot of administration. If the unallocation runs in the background, the performance of foreground process increases. Unfortunatelly, memory allocation can't be lazy (the objects allocated will be used at the holy next moment), but releasing objects can.

Try in C++ (w/o any GC) to allocate a big bunch of objects, print "hello", then delete them. You'll be surprised how long does it take to free objects.

Also, GNU libc provides more effective tools for unallocating memory, see obstacks. Must notice, I have no experience with obstacks, I never used them.

14
  • In principle you have a point but it should be noted that this is an issue that has a very simple solution: use a pool allocator or small object allocator to bundle deallocations. But this admittedly takes (slightly) more effort than having a GC run in background. Dec 27, 2010 at 14:29
  • Yep, sure, GC is much more comfortable. (Especially for beginners: there're no ownership problems, there's even no delete operator.)
    – ern0
    Dec 27, 2010 at 14:46
  • 3
    @ern0: no. The whole point of (reference counting) smart pointers is that there is no ownership problem and no delete operator. Dec 27, 2010 at 14:57
  • 3
    @Jon: which, honestly, is most of the time. If you willy-nilly share object state between different threads you will have completely different problems. I’ll admit that many people program that way but this is a consequence of the bad threading abstractions that have existed until recently and it’s not a good way to do multithreading. Dec 27, 2010 at 15:32
  • 1
    Deallocation often isn't done "in the background", but rather pauses all foreground threads. Batch-mode garbage-collection is generally a performance win, despite the pausing of foreground threads, because it allows unused space to be consolidated. One could separate the processes of garbage-collection and heap compactification, but--especially in frameworks that use direct references rather than handles--they both tend to be "stop-the-world" processes, and it's often most practical to do them together.
    – supercat
    Jul 10, 2012 at 19:21
2

It's a spectrum.

If you wan't tight bounds on performance and are prepared to put the grind in, you'll end up at assembly or c, with all the onus on you to make the right decisions and all the freedom to do that, but with it, all the freedom to mess it up:

"I'll tell you what to do, you do it. Trust me".

Garbage collection is the other end of the spectrum. You have very little control, but its taken care of for you:

"I'll tell you what I want, you make it happen".

This has a lot of advantages, mostly that you don't need to be as trustworthy when it comes to knowing exactly when a resource is no longer needed, but (despite some of the answers floating around here) is not good for performance, and the predictability of performance. (Like all things, if you are given control, and do something stupid you can have worse results. However to suggest that knowing at compile time what the conditions are for being able to free memory, can't be use as a performance win is beyond naive).

RAII, scoping, ref counting, etc are all helpers for letting you move further along that spectrum but its not all the way there. All of these things still require active use. They still let and require you to interact with memory management in a way that garbage collection does not.

1

Number one limitation of smart pointers is they don't always help against circular references. For example you have object A storing a smart pointer to object B and object B is storing a smart pointer to object A. If they are left together without resetting either of the pointers they won't ever be deallocated.

This happens because a smart pointer has to perform a specific action which won't be triigered in the above scenario because both objects are unreacheable to the program. Garbage collection will cope - it will properly identify that objects are not reachecable to the program and they will be collected.

0

Please remember that in the end, everything boils down to a CPU executing instructions. To my knowledge all consumer grade CPUs have instruction sets which require you to have data stored in a given place in memory and you have pointers to said data. That's all that you have at the basic level.

Everything on top of that with garbage collection, references to data which may have been moved, heap compaction, etc. etc. is doing the work within the restrictions given by the above "memory chunk with an address pointer" paradigm. Same thing with smart pointers - you STILL have to make the code run on actual hardware.

Not the answer you're looking for? Browse other questions tagged or ask your own question.