On Wednesday, 7 December 2022 at 01:46:21 UTC, Siarhei Siamashka
wrote:
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:
```D
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](https://www.sqlite.org/c3ref/column_blob.html)
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](https://dlang.org/library/std/string/from_stringz.html) as suggested by H. S. Teoh would avoid allocation, but you need to be careful with it:
```D
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](https://forum.dlang.org/post/mailman.2828.1670270281.31357.digitalmar...@puremagic.com).
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](https://dlang.org/spec/memory-safe-d.html) (and use
the "-dip1000" compiler command line option too). If you do
this, then the compiler will start complaining a lot. Try it:
```D
@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?
That's a good point, but there also no error checking for the
sqlite return, but that's not the point, ``std.conv.to!string``
doesn't teach about the difference between c string and d string,
it hides the magic
Doing it manually teaches it, that was the point