printf, writeln, writefln

ryuukk_ ryuukk.dev at gmail.com
Wed Dec 7 03:50:20 UTC 2022


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.digitalmars-d@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


More information about the Digitalmars-d-learn mailing list