printf, writeln, writefln

Siarhei Siamashka siarhei.siamashka at gmail.com
Wed Dec 7 01:46:21 UTC 2022


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?


More information about the Digitalmars-d-learn mailing list