Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

I really like scope-based memory management (SBMM), or RAII, as it is more commonly (confusingly?) referred to by the C++ community. As far as I know, except for C++ (and C), there's no other mainstream language in use today that makes SBMM/RAII their main memory management mechanism, and instead they prefer to use garbage collection (GC).

I find this rather confusing, since

  1. SBMM makes programs more deterministic (you can tell exactly when an object is destroyed);
  2. in languages that use GC you often have to do manual resource management (see closing files in Java, for example), which partly defeats the purpose of GC and is also error prone;
  3. heap memory can also (very elegantly, imo) be scope-bound (see std::shared_ptr in C++).

Why is not SBMM more widely used? What are its disadvantages?

share|improve this question
    
Some disadvantages (especially concerning speed) are discussed in Wikipedia: en.wikipedia.org/wiki/… –  Philipp Mar 9 at 14:29
2  
Java's manual resource management problem is a side effect of not guaranteeing that an object's finalize() method will be called prior to garbage collection. In effect, this creates the same class of problem that garbage collection is supposed to solve. –  Blrfl Mar 9 at 15:00
5  
@Blrfl Nonsense. "Manual" resource management (for resources other than memory, obviously) would be preferable even if that "problem" did not exist, because GC can run a very long time after the resource becomes unused, or even not run at all. That's no problem for memory, and memory management is all that garbage collection is supposed to solve. –  delnan Mar 9 at 15:23
3  
btw. I like to refer to it as SBRM since you can use the same mechanism to manage resources in general, not just memory. –  PlasmaHH Mar 10 at 10:48
1  

7 Answers 7

Let's start by postulating that memory is by far (dozens, hundreds or even thousands of time) more common than all other resources combined. Every single variable, object, object member needs some memory allocated to it and freed later on. For every file you open, you create dozens to millions of objects to store the data pulled out of the file. Every TCP stream goes together with an unbounded number of temporary byte strings created to be written to the stream. Are we on the same page here? Great.

For RAII to work (even if you have ready-made smart pointers for every use case under the sun), you need to get ownership right. You need to analyse who should own this or that object, who should not, and when ownership should be transferred from A to B. Sure, you could use shared ownership for everything, but then you'd be emulating a GC via smart pointers. At that point it becomes much easier and faster to build the GC into the language.

Garbage collection frees you from this concern for the by far most commonly used resource, memory. Sure, you still need to make the same decision for other resources, but those are far less common (see above), and complicated (e.g. shared) ownership is less common too. The mental burden is reduced significantly.

Now, you name some downsides to making all values garbage collected. However, integrating both memory-safe GC and value types with RAII into one language is extremely hard, so perhaps it's better to migitate these trade offs via other means?

The loss of determinism in turns out to be not that bad in practice, because it only affects deterministic object lifetime. As described in the next paragraph, most resources (aside from memory, which is plentiful and can be recycled rather lazily) are not bound to object lifetime in these languages. There are a few other uses cases, but they are rare in my experience.

Your second point, manual resource management, is nowadays addressed via a statement that does perform scope-based cleanup, but does not couple this clean up to the object life time (hence not interacting with the GC and memory safety). This is using in C#, with in Python, try-with-resources in recent Java versions.

share|improve this answer
    
This does not explain why nondeterministic models like GC should be superior to deterministic ones. using statements are only possible locally. It's impossible to clean up resources held in member variables that way. –  Philipp Mar 9 at 14:25
7  
@Philipp Shared ownership for everything is a GC, just a very poor one. If you take "shared ownership" to imply ref counting, please say so, but keep the discussion about cycles in the comments on amon's answer. I'm also not sure whether ref counting is deterministic in the sense OP is interested in (objects are freed as early as possible, discounting cycles, but you often can't tell when that is by looking at the program). Moreover, ref counting for everything is slow, much slower than a modern tracing GC. –  delnan Mar 9 at 14:42
10  
using is a joke compared to RAII, just so you know. –  DeadMG Mar 10 at 0:10
1  
to using, with and try-with (on C#, Python and Java, respectively) you can add defer in Go. –  Javier Mar 10 at 3:49
3  
@Philipp please describe your metric for "superior". It is true that manual memory management is faster at run-time for dealing with memory management. However the cost of software cannot be judged purely on the CPU time spent on memory management alone. –  ArTs Mar 10 at 7:29

RAII also follows from automatic reference-counting memory management, e.g. as used by Perl. While reference counting is easy to implement, deterministic, and quite performant, it cannot deal with circular references (they cause a leak) which is why it isn't commonly used.

Garbage-collected languages can't use RAII directly, but often do offer syntax with an equivalent effect. In Java, we have the try-with-ressource statement

try (BufferedReader br = new BufferedReader(new FileReader(path))) { ... }

which automatically calls .close() on the resource on block exit. C# has the IDisposable interface, which allows .Dispose() to be called when leaving an using (...) { ... } statement. Python has the with statement:

with open(filename) as f:
    ...

which works in a similar fashion. In an interesting spin on this, Ruby's file open method gets a callback. After the callback has been executed, the file is closed.

File.open(name, mode) do |f|
    ...
end

I think Node.js uses the same strategy.

share|improve this answer
4  
Using higher order functions for resource management dates back long before Ruby. In Lisps, it is quite common to have, say, with-open-filehandle functions which open the file, yield it to a function and upon return of the function close the file again. –  Jörg W Mittag Mar 9 at 14:15
3  
The cyclic reference argument is quite common, but how important is it really? Cyclic references can be mitigated using weak pointers if ownership is clear. –  Philipp Mar 9 at 14:23
2  
@Philipp When you use ref counting, ownership usually isn't clear. Also, this answer talks about languages which use ref counting exclusively and automatically, so weak references either don't exist or are much harder to use than strong references. –  delnan Mar 9 at 14:28
2  
@Philipp cyclic data structures are very rare, unless you're working with complicated graphs anyway. Weak pointers do not help in a general cyclic object graph, although they help in more common cases like parent pointers in a tree. A good workaround is to keep a context object that represents the reference to the whole graph, and manages destruction. Refcounting is not a dealbraker, but it requires that the programmer is very aware of its restrictions. I.e. it has a slightly higher cognitive cost than GC. –  amon Mar 9 at 14:28
    
@amon "cyclic data structures are very rare, unless you're working with complicated graphs anyway." Persistent data structures are one instance where you might have complicated graphs. On the other hand, if you're programming in C/C++ you probably don't care that much about referential transparency. –  Doval Mar 10 at 19:35

In my opinion the most convincing advantage of garbage collection is that it allows for composability. Correctness of memory management is a local property in garbage collected environment. You can look at each part in isolation and determine if it can leak memory. Combine any number of memory-correct parts and they stay correct.

When you rely on reference counting you lose that property. Whether your application can leak memory becomes a global property of the whole application with reference counting. Every new interaction between parts has the possibility to use the wrong ownership and break memory management.

It has a very visible effect on the design of programs in the different languages. Programs in GC-languages tend to be a bit more soups of objects with lots of interactions, while in GC-less languages one tends to prefer structured parts with strictly controlled and limited interactions between them.

share|improve this answer
    
Correctness is only composable if objects only hold references on their own behalf, and not on behalf of the targets of those references. Once things like notifications enter into the mix (Bob holds a reference to Joe because Joe asked Bob to notify him when something happened and Bob promised to do so, but Bob otherwise doesn't care about Joe), GC correctness often requires scoped resource management [manually implemented in many cases, since GC systems lack the automation of C++]. –  supercat Mar 18 at 23:46

Closures are an essential feature of pretty much all modern languages. They are very easy to implement with GC and very hard (though not impossible) to get right with RAII, since one of their main features is that they allow you to abstract over the lifetime of your variables!

C++ only got them 40 years after everybody else did, and it took a lot of hard work by a lot of smart people to get them right. In contrast, many scripting languages designed and implemented by people with zero knowledge in designing and implementing programming languages have them.

share|improve this answer
5  
I don't think closures in C++ are a good example. The lambdas in C++11 are just syntactic sugar for functor classes (which pre-date C++11 significantly), and are equally memory-unsafe: If you capture something by reference, and call the closure after that thing goes dead, you simply get UB, much like holding onto a reference longer than valid. That they appeared 40 years late is due to belated acknowledgement of FP, not due to figuring out how to make them safe. And while designing them was certainly a huge task, I doubt that most of the effort went into life time considerations. –  delnan Mar 9 at 14:22
    
I agree with delnan: C++ did not get closures right: you have to program them very carefully if you do not want to get a core dump when you invoke them. –  Giorgio Mar 9 at 15:49
1  
@delnan: capture-by-reference lambda's very intentionally have that [&] syntax. Any C++ programmer already associates the & sign with references and knows about stale references. –  MSalters Mar 10 at 12:50
1  
@MSalters What's your point? I've drawn the reference connection myself. I did not say C++ lambdas are exceptionally unsafe, I said they are exactly as unsafe as references. I did not argue that C++ lambdas are bad, I argued against this answer's claim (that C++ got closures very late because they had to figure out how to do it right). –  delnan Mar 10 at 13:02

Looking at some popularity index like TIOBE (which is arguable, of course, but I guess for your kind of question its ok to use this) , you first see that ~50% of the top 20 are "scripting languages" or "SQL dialects", where the "ease of use" and means of abstraction have a much more importance than deterministic behaviour. From the remaining "compiled" languages, there are around 50% of the languages with SBMM and ~50% without. So when taking the scripting languages out of your calculation, I would say your assumption is just wrong, among compiled languages the ones with SBMM are as popular as the ones without.

share|improve this answer
    
How is "ease of use" different from determinism? Shouldn't a deterministic language be considered easier to use than a non-deterministic one? –  Philipp Mar 9 at 14:21
2  
@Philipp Only of the thing that's deterministic or not actually matters. Object life time doesn't matter on its own (though C++ and friends tie many things which matter to object life time, because they can). When an unreachable object is freed doesn't matter because, by definition, you aren't using it any more. –  delnan Mar 9 at 14:25
    
One more thing, various "scripting languages" like Perl and Python also use reference counting as primary means for memory management. –  Philipp Mar 10 at 12:57
    
@Philipp At least in the Python world, this is considered an implementation detail of CPython, not a property of the language (and virtually every other implementation eschews refcounting). Moreover, I would argue that catch-all no-opt-out reference counting with backup cycle GC does not qualify as SBMM or RAII. In fact, you'd be hard pressed to find RAII proponents that are consider this style of memory management comparable to RAII (mostly because it isn't, cycles anywhere can prevent prompt deallocation anywhere else in the program). –  delnan Mar 10 at 14:26

One major advantage of a GC system which nobody has mentioned yet is that a reference in a GC system is guaranteed to retain its identity as long as it exists. If one calls IDisposable.Dispose (.NET) or AutoCloseable.Close (Java) on an object while copies of the reference exist, those copies will continue to refer to the same object. The object won't be useful for anything anymore, but attempts to use it will have predictable behavior controlled by the object itself. By contrast, in C++, if code calls delete on an object and later tries to use it, the entire state of the system becomes totally undefined.

Another important thing to note is that scope-based memory management works very well for objects with clearly-defined ownership. It works much less well, and sometimes downright badly, with objects that have no defined ownership. In general, mutable objects should have owners, while immutable objects don't need to, but there's a wrinkle: it's very common for code to use an instance of a mutable types to hold immutable data, by ensuring that no reference will be exposed to code that might mutate the instance. In such a scenario, instances of the mutable class might be shared among multiple immutable objects, and thus have no clear ownership.

share|improve this answer
2  
The property you refer to in the first half is memory safety. While a GC is a very easy way to achieve memory safety, a GC is not necessary: Look at Rust for a well-done example. –  delnan Mar 10 at 0:37
    
@delnan: When I view rust-lang.org, my browser can't seem to navigate usefully from there; where should I look for more information? My impression is that memory safety without GC imposes certain restrictions on data structures which may not fit well with all the things an application might need to do, but I'd be happy to be proven wrong. –  supercat Mar 10 at 2:53
    
I don't know of a single (or even small set of) good references for this; my Rust knowledge has accumulated over a year or two of reading the mailing list (and all the stuff linked to in mails, including various tutorials, language design blog posts, github issues, ThisWeekInRust, and more). To briefly address your impression: Yes, each safe construct (necessarily) impose restrictions, but for virtually any memory safe piece of code, an appropriate safe construct exists or can be written. The by far most common ones already exist in language and stdlib, all others can be written in user code. –  delnan Mar 10 at 12:26
    
@delnan: Does Rust require interlocked updates to reference counters, or does it have some other means of dealing with immutable objects (or immutably-wrapped mutable objects) which have no definite ownership? Does Rust have a concept of both "object-owning" and "non-owning" pointers? I recall a paper on "Xonor" pointers which discussed the idea of objects having a single reference that "owns" them, and other references which do not; when the "owning" reference goes out of scope, all non-owning references would become references to dead objects, and would be identifiable as such... –  supercat Mar 10 at 16:24
    
...for as long as they exist. IMHO, that's the "right" general abstraction for mutable object instances (though not all instances of mutable types!); generally, as long as an object instance is mutable, it should have a single well-defined "owner". Except in the scenario where the owner mutates the object on creation and then intends for it to be immutable after that, any use of a mutable object after the owner has abandoned it is apt to be a mistake. –  supercat Mar 10 at 16:29

First off, its very important to realize that equating RAII to SBMM. or even to SBRM. One of the most essential (and least known or most under appreciated) qualities of RAII is the fact that it makes 'being a resource' a property that is NOT transitive to composition.

The following blog post discusses this important aspect of RAII and contrasts it to resource amangement in GCed languages that use non-deterministic GC.

http://minorfs.wordpress.com/2011/04/29/why-garbage-collection-is-anti-productive/

Its important to note that while RAII is mostly used in C++, Python (at last the non-VM based version) has destructors and deterministic GC that allows RAII to be used together with GC. Best of both worlds if it were.

share|improve this answer
    
+1 Interesting. I don't necessarily agree with his final conclusion that GC reduces productivity, but he does make a valid point that I haven't considered before (at least not as clearly as he put it in the article). –  Paul Mar 15 at 9:24

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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