Not knowing whether the question was "what have you used", "what do you think is a good solution" or "what will prevent me from killing you if you ask me to debug or maintain your code" I've gone for a union of the first two. OK, technically I've written a cgi program which took the "Never write a long-running program, so exit() does the job." approach, but I commented the leak. (With /*This leaks. Watch me care.*/)
Obviously it depends on what kind of thing you're writing. For a console game, pool-based allocation is great: have a pool for each game, level, or frame, fill it at the start of the game/level/frame, then throw it away at the end. It's considered rude to run out of memory, so you calculate in advance the maximum amount of space you'll need.
This strategy would not be so good for other kinds of application...
Personally I'm happy to use explicit free and deal with the occasional tracking-down-the-leak job. I'll occasionally supplement that with a spot of reference counting when it looks like the simplest approach - there's some in Puzzles, for example - but it's unlikely to be used as a universal approach to memory management of an entire program; in Puzzles I only use it for one or two specific data structures where it happens to make the most sense. And I'm at least as likely to reference-count for purposes other than garbage collection: in Tweak I use it for copy-on-write.
Pool allocators sound like a neat idea; I've used them in projects I didn't originate, and I might use one in anger if I ever happen to be working on a project which has the right shape. Like reference counting, though, they suit particular tasks better than others; your job has to be divided into neat segments which can have a pool each, and some jobs just aren't that neatly structured. So if either of the fancy techniques looks iffy, I'll just fall straight back to lots of explicit frees. It's not that bad really.
(In particular, although true GC works well in functional languages, I've never trusted it for mutable data. I always fear that I'll leave a stale pointer lying around and accidentally keep pouring valuable data into a structure nobody will ever read it back out of. In C, I can free that structure and then valgrind/efence/inexplicable crashing will let me know of my mistake; in a GC language I might never notice!)
I think it's often better to leak in short running processes than to explicitly free and get it wrong. All the reference county stuff a la GTK is just even more opportunity to do the same.
If a process actually stands a chance of consuming a significant amount of heap in its lifetime and if that lifetime is not to the end of the process, then I'd put it in a pool allocator type arrangement. Most long-running processes have domains for which you can say "all the stuff allocated under this flag can now be freed", and that gives you bounded overuse. I'd not class Boehm as a proper garbage collector [:)], it can (and does) come back to bite you at unexpected times.
One of my big disappointments with gcj is that it uses the Boehm collector.
If I'm writing a execute once program then I might allocate the memory and let exit() sort it all out at the end. Typically the majority of the memory is needed until almost the last moment, anyway, so the extra overhead isn't a killer.
If I'm writing a daemon then I try very damned hard to free memory ASAP.
If I'm writing a daemon that uses ldap(3) routines then I curse a lot because it seems every routine has a unique way of allocating memory and has a correspending free routine to go with it. ldap_msgfree() free() ldap_value_free() ldap_memfree() ber_free() And that's just in a 354 line program that doesn't do anything very clever!
I've written programs that allocated sufficient memory for their entire configuration script then chopped it up into tokens and took (const char *)s to some of them. This leaked the rest of the configuration file; I didn't care.
I've written a program that never deallocated anything. The way to shrink its memory use was to save, quit, start, load. It did this automatically once in a while.
Explicitly free()ing in the right places is my normal M.O. when coding in C.
I've never used pool allocation as the sole means of garbage collection, but it's just-about the only way I know of to be sure all the heap-allocated objects that are private to a thread get vaped when the thread is killed.
Reference counting (boost::shared_ptr) is something I use frequently in C++. It's especially handy as a substitute for carefully defining (and expecting maintainers to understand) transfer-of-ownership semantics across module boundaries, as well as in symbol tables, functor bindings, etc.
I wistfully long for the days when I coded in a garbage-collected language. Then I remember a lot of the audio processing code I write has real-time performance constraints. By having explicit control over allocation policies, I can be certain the SCHED_RR thread never allocates or deallocates, merely uses buffers managed from normal threads. While it's what I'd use most of the time if I could, it's not a panacea, especially when you care which thread gets to tidy up a resource (destruct an object, whatever).
I rely extensively on C++'s ability to manage automatic (i.e. stack-based) resources when exceptions occur; through container classes, boost::scoped_ptr or, if I must std::auto_ptr, this can also manage the lifetime of objects on the heap.
So… I'd like more GC, but it would be a show-stopping problem if that was the only mechanism I had.
The ldap thing doesn't seem so unusual; at work we have a huge pile of types with automatically generated free routines (among other things). The free routines have names consistent with the types though, and you can also free using a generic routine and a type pointer, making it pretty easy to find the right one, even if you're writing a type-agnostic macro or function. If your ldap library doesn't have at least the consistent naming then I suggest a pile of wrapper macros.
The alternative[1] would be to teach each object representation its own type so that a generic free routine could figure out how to free it - but then you need type-specific initialization, so it's not clear you really gain much.
Personally I get a bit twitchy about not knowing exactly when memory will be freed and objects, should the be present, be destroyed[1] so I am a little wary of garbage collection for garbage collection's sake.
Generally I'm happy if there is a well-known, upheld, convention on memory. My current favourite being 'you created it, you should free it'. This nicely maps into reference counting ('you referenced it, you should de-reference it') like the way reference counting works in OS X's Objective C APIs (which is about my personal sweet spot on the abstraction vs explicit free() curve).
Pool allocators are also pretty hoopy in certain circumstances (especially with some of the $#"(*$ latencies on creation of certain resources in Win32).
[1] Even hacks like IDisposable interfaces in CLR et al don't really make me feel better.
(no subject)
Date: 2006-03-27 04:51 pm (UTC)(no subject)
Date: 2006-03-27 05:01 pm (UTC)(no subject)
Date: 2006-03-27 05:09 pm (UTC)/*This leaks. Watch me care.*/)(no subject)
Date: 2006-03-27 05:51 pm (UTC)This strategy would not be so good for other kinds of application...
(no subject)
Date: 2006-03-27 05:57 pm (UTC)Pool allocators sound like a neat idea; I've used them in projects I didn't originate, and I might use one in anger if I ever happen to be working on a project which has the right shape. Like reference counting, though, they suit particular tasks better than others; your job has to be divided into neat segments which can have a pool each, and some jobs just aren't that neatly structured. So if either of the fancy techniques looks iffy, I'll just fall straight back to lots of explicit frees. It's not that bad really.
(In particular, although true GC works well in functional languages, I've never trusted it for mutable data. I always fear that I'll leave a stale pointer lying around and accidentally keep pouring valuable data into a structure nobody will ever read it back out of. In C, I can free that structure and then valgrind/efence/inexplicable crashing will let me know of my mistake; in a GC language I might never notice!)
(no subject)
Date: 2006-03-27 05:58 pm (UTC)If a process actually stands a chance of consuming a significant amount of heap in its lifetime and if that lifetime is not to the end of the process, then I'd put it in a pool allocator type arrangement. Most long-running processes have domains for which you can say "all the stuff allocated under this flag can now be freed", and that gives you bounded overuse. I'd not class Boehm as a proper garbage collector [:)], it can (and does) come back to bite you at unexpected times.
One of my big disappointments with gcj is that it uses the Boehm collector.
(no subject)
Date: 2006-03-27 09:58 pm (UTC)If I'm writing a daemon then I try very damned hard to free memory ASAP.
If I'm writing a daemon that uses ldap(3) routines then I curse a lot because it seems every routine has a unique way of allocating memory and has a correspending free routine to go with it.
ldap_msgfree()
free()
ldap_value_free()
ldap_memfree()
ber_free()
And that's just in a 354 line program that doesn't do anything very clever!
(no subject)
Date: 2006-03-28 12:53 am (UTC)I've written a program that never deallocated anything. The way to shrink its memory use was to save, quit, start, load. It did this automatically once in a while.
Explicitly free()ing in the right places is my normal M.O. when coding in C.
I've never used pool allocation as the sole means of garbage collection, but it's just-about the only way I know of to be sure all the heap-allocated objects that are private to a thread get vaped when the thread is killed.
Reference counting (boost::shared_ptr) is something I use frequently in C++. It's especially handy as a substitute for carefully defining (and expecting maintainers to understand) transfer-of-ownership semantics across module boundaries, as well as in symbol tables, functor bindings, etc.
I wistfully long for the days when I coded in a garbage-collected language. Then I remember a lot of the audio processing code I write has real-time performance constraints. By having explicit control over allocation policies, I can be certain the SCHED_RR thread never allocates or deallocates, merely uses buffers managed from normal threads. While it's what I'd use most of the time if I could, it's not a panacea, especially when you care which thread gets to tidy up a resource (destruct an object, whatever).
I rely extensively on C++'s ability to manage automatic (i.e. stack-based) resources when exceptions occur; through container classes, boost::scoped_ptr or, if I must std::auto_ptr, this can also manage the lifetime of objects on the heap.
So… I'd like more GC, but it would be a show-stopping problem if that was the only mechanism I had.
(no subject)
Date: 2006-03-28 01:34 am (UTC)(no subject)
Date: 2006-03-28 08:05 am (UTC)The ldap thing doesn't seem so unusual; at work we have a huge pile of types with automatically generated free routines (among other things). The free routines have names consistent with the types though, and you can also free using a generic routine and a type pointer, making it pretty easy to find the right one, even if you're writing a type-agnostic macro or function. If your ldap library doesn't have at least the consistent naming then I suggest a pile of wrapper macros.
The alternative[1] would be to teach each object representation its own type so that a generic free routine could figure out how to free it - but then you need type-specific initialization, so it's not clear you really gain much.
[1] in C - in C++ you have other possibilities.
(no subject)
Date: 2006-03-28 08:55 am (UTC)Generally I'm happy if there is a well-known, upheld, convention on memory. My current favourite being 'you created it, you should free it'. This nicely maps into reference counting ('you referenced it, you should de-reference it') like the way reference counting works in OS X's Objective C APIs (which is about my personal sweet spot on the abstraction vs explicit free() curve).
Pool allocators are also pretty hoopy in certain circumstances (especially with some of the $#"(*$ latencies on creation of certain resources in Win32).
[1] Even hacks like IDisposable interfaces in CLR et al don't really make me feel better.