GC conservatism -- again

Adam Ruppe destructionator at gmail.com
Thu Dec 30 08:38:00 PST 2010


On 12/27/10, Steven Schveighoffer <schveiguy at yahoo.com> wrote:
> What about tools to make deallocation easier?  For example, we have
> scope(exit) that you could potentially use to ensure a memory block is
> deallocated on exit from a scope, what about a thread exit?

It seems to me that the simplest thing might simply be a list of delegates stored
with the thread:

thread.onexit ~= { free(whatever); };

> Any other ideas?

Perhaps a new array type that behaves mostly like a built in (offering slices,
appending, etc.) except it is built off C's malloc and realloc and thus can and
must be manually freed. If the contents are fancy, they can still be garbage
collected, but the array itself is manual.

I guess you could also use D's own new and delete on arrays, but delete is
discouraged I guess so sticking to C's functions for that is probably for the
best. Besides, with a custom struct, we can disable some
operations that might make manual management hard so you don't
accidentally keep bad pointers around.

Something along these lines:

====
import std.traits;
import core.stdc.stdlib;

ManualArray!(T) manual(T)(size_t size, size_t capacity = 0) if( !hasAliasing!(T) ) {
        if(capacity == 0)
                capacity = size; // or whatever a sane default is

        // the array and room for length and capacity right before it
        ManualArray!(T) arr;

        arr.length = size;
        arr.capacity = capacity;
        arr.payload = malloc(T.sizeof * capacity);

        if(arr.payload is null)
                throw new OutOfMemoryError();

        return arr;
}

// FIXME: if it does have aliasing, add this block as a gc root too so the objects
// within get regular treatment

struct ManualArray(T) {
        // @disable this() {} // if we could disable the default constructor...
        @disable this(this) { } // don't copy me without specific intention

        typeof(this) opBinary(string op)(T rhs) if( op == "~" ) {
                static assert(0, "This kind of concat might get messy so it is
disabled.");
        }

        typeof(this) opOpAssign(string op)(T rhs) if( op == "~") {
                if(length + 1 > capacity) {
                        capacity = capacity * 2; // or whatever is sane
                        auto npayload = realloc(payload, capacity * T.sizeof);
                        if(npayload is null)
                                throw new OutOfMemoryError();

                        payload = npayload;
                }

                payload[ length++ ] = rhs;

                return this;
        }

        // flesh out other ops so this thing doesn't suck, like append array,
        // append typeof(this), slicing, index, dup, etc. If you take a slice of
the whole
        // array, you'd be able to pass that around as a D array. Use care if you do.

        size_t length;
        size_t capacity;

        T* payload; // a C style array
}

void free(ref ManualArray!T item) {
        .free(item.payload);
        item.length = item.capacity = 0;
        item.payload = null;
}
====


That probably won't even compile, but you can see where I'm going - give a C
style array some D nicities, but it is still managed as in C, with the exception
that some operations are disabled in an attempt to keep unintended aliasing
to a minimum. Naturally, slicing would work against that, but it's just too useful
to not provide. Maybe a const slice is a good compromise - we don't
want them to append to a slice since I don't know how that would break things,
being on the C heap and all - but you'd still have access to the contents for D
functions.


More information about the Digitalmars-d mailing list