Remember all the fun we had last year when I failed to heed the
warning in the toStringz documentation about retaining a
reference to a char * passed into C? It took a long time to find
that one, with a lot of help from Steve Schveighoffer and others.
Well, I've got another one. Consider this:
````
// Get number of children of the parent account
auto gc_protect = bind_text(n_children_stmt, 1,
parent.guid);
parent.children.length = one_row!(int)(n_children_stmt,
&get_int);
auto gc_protect2 = bind_text(account_child_stmt, 1,
parent.guid);
for (int i = 0; next_row_available_p(account_child_stmt,
&sqlite3_reset); i++) {
parent.children[i] = new Account;
parent.children[i].name =
fromStringz(sqlite3_column_text(account_child_stmt, 0)).idup;
parent.children[i].guid =
fromStringz(sqlite3_column_text(account_child_stmt, 1)).idup;
parent.children[i].flags =
sqlite3_column_int(account_child_stmt, 2);
parent.children[i].value =
get_account_value(parent.children[i]);
}
````
bind_text takes a D string, turns it into a C string with
toStringz, uses that to call sqlite3_bind_text and returns the C
string, which I store as you can see with the intention of
protecting it from the gc. The code as written above does not
work. At some point, I get an index-out-of-bounds error, because
the loop is seeing too many children. If I turn off the GC, the
code works correctly and the application completes normally.
With the GC on, if I put a debugging writeln inside the loop,
right after the 'for', that prints, among other things, the value
of gc_protect2 (I wanted to convince myself that the GC wasn't
moving what it points to; yes, I know the documentation says the
current GC won't do that), the problem goes away. A Heisenbug!
My theory: because gc_protect2 is never referenced, I'm guessing
that the compiler is optimizing away the storage of the returned
pointer, the supporting evidence being what I said in the
previous paragraph. Anyone have a better idea?
By the way, I get the same error compiling this with dmd or ldc.
/Don Allen