On Tuesday, 6 December 2022 at 23:07:32 UTC, johannes wrote:
> //-- the result should be f.i. "the sun is shining"
//-- sqlite3_column_text returns a constant char* a \0 delimited c-string
printf("%s\n",sqlite3_column_text(res, i));
writeln(sqlite3_column_text(res, i));
writefln("%s",sqlite3_column_text(res, i));
writefln(std.conv.to!string(sqlite3_column_text(res, i)));
//-- the result :
the sun is shining
55B504B3CE98
55B504B3CE98
the sun is shining
=> without 'std.conv.to!string' I presume 'write' prints out the address of the first byte. This is odd to me because printf does the job correctly. But I think to understand that write in D interpretes char* as a pointer to a byte. So it prints the address that the pointer points to.(?)
So the question : for every c funtion returning char* I will have to use std.conv.to!string(..) or is there another way. Also it seems the formatting "%s" has another meaning in D ?
What you are posting here is a perfect example of unsafe code. Let's looks at it:
import std;
char *sqlite3_column_text() @system {
return cast(char *)"the sun is shining".ptr;
}
void main() {
printf("%s\n",sqlite3_column_text());
writeln(sqlite3_column_text());
writefln("%s",sqlite3_column_text());
writefln(std.conv.to!string(sqlite3_column_text()));
}
Unsafe code has a lot of rules, which have to be carefully followed if you want to stay out of troubles. It's necessary to check the sqlite3 documentation to see who is responsible for deallocating the returned c-string and what is its expected lifetime (basically, the sqlite3 library takes care of managing the returned memory buffer and it's valid only until you make some other sqlite3 API calls).
So the use of "printf" is fine.
Direct "writeln"/"writefln" prints the pointer value, which also fine (but not what you expect).
Doing "std.conv.to!string" allocates a copy of the string, managed by the garbage collector (and this may be undesirable for performance reasons).
Doing std.conv.fromStringz as suggested by H. S. Teoh would avoid allocation, but you need to be careful with it:
char[] s1 = fromStringz(sqlite3_column_text());
writeln(s1); // everything is fine
char[] s2 = fromStringz(sqlite3_column_text()); // some other sqlite3 API call
writeln(s1); // oops, sqlite3 may have already messed up s1
Depending on what sqlite3 is doing under the hood, this may be a good example of "use after free" security issue discussed in another forum thread.
But hold on! Isn't D a safe language, supposed to protect you from danger? The answer is that it is safe, but the compiler will only watch your back if you explicitly annotate your code with a @safe attribute (and use the "-dip1000" compiler command line option too). If you do this, then the compiler will start complaining a lot. Try it:
@safe:
import std;
char *sqlite3_column_text() @system {
return cast(char *)"the sun is shining".ptr;
}
string trusted_sqlite3_column_text() @trusted {
return std.conv.to!string(sqlite3_column_text());
}
void main() {
printf("%s\n",sqlite3_column_text()); // Nay!
writeln(sqlite3_column_text()); // Nay!
writefln("%s",sqlite3_column_text()); // Nay!
writefln(std.conv.to!string(sqlite3_column_text())); // Nay!
writeln(trusted_sqlite3_column_text()); // Yay!
}
The "trusted_sqlite3_column_text" wrapper function does a memory allocation and it's up to you to decide whether this is acceptable. You are always free to write unsafe code tuned for best performance (annotated as @system or @trusted), but it's your own responsibility if you make a mistake.
BTW, maybe it's even more efficient to use sqlite3_column_blob() and sqlite3_column_bytes() for retrieving the column text as a string?