Jump to page: 1 2
Thread overview
printf, writeln, writefln
Dec 06, 2022
johannes
Dec 06, 2022
H. S. Teoh
Dec 07, 2022
ryuukk_
Dec 07, 2022
ag0aep6g
Dec 08, 2022
Salih Dincer
Dec 08, 2022
Ali Çehreli
Dec 08, 2022
Salih Dincer
Dec 10, 2022
Nick Treleaven
Dec 18, 2022
Salih Dincer
Dec 08, 2022
Salih Dincer
Dec 07, 2022
Siarhei Siamashka
Dec 07, 2022
ryuukk_
Dec 07, 2022
Ali Çehreli
Dec 08, 2022
johannes
December 06, 2022
//-- 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 ?
December 06, 2022
On Tue, Dec 06, 2022 at 11:07:32PM +0000, johannes via Digitalmars-d-learn 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));
[...]

In D, strings are not the same as char*.  You should use std.conv.fromStringZ to convert the C char* to a D string.


T

-- 
For every argument for something, there is always an equal and opposite argument against it. Debates don't give answers, only wounded or inflated egos.
December 07, 2022

On Tuesday, 6 December 2022 at 23:41:09 UTC, H. S. Teoh wrote:

>

On Tue, Dec 06, 2022 at 11:07:32PM +0000, johannes via Digitalmars-d-learn 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));
[...]

In D, strings are not the same as char*. You should use std.conv.fromStringZ to convert the C char* to a D string.

T

no, you don't "need" to use std conv

Here is an alternative that doesn't allocate

Make sure to read the comments


// here notice where the functions are comming from
import std.stdio : writeln, writefln;

import core.stdc.stdio : printf;
import core.stdc.string : strlen;

const(char)* sqlite3_column_text(void*, int iCol)
{
    return "hello world";
}

void main()
{
    // printf is a libc function, it expects a null terminated string (char*)
    printf("%s\n", sqlite3_column_text(null, 0));

    // writeln is a d function, it expects a string (immutable slice of char aka immutable(char)[], a slice is T* + len)

    // so here we get the string from sqlite
    const(char)* ret = sqlite3_column_text(null, 0);


    // then we need to determine the length of the null terminated string from c
    // we can use strlen from libc

    size_t len = strlen(ret);

    // now we got a len, we can build a D string, remember, it's just a slice

    string str = cast(string) ret[0 .. len];

    writeln(str);
    writefln("%s", str);
}
December 07, 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:

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?

December 07, 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:

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?

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

December 07, 2022
On 07.12.22 01:35, ryuukk_ wrote:
> On Tuesday, 6 December 2022 at 23:41:09 UTC, H. S. Teoh wrote:
[...]
>> In D, strings are not the same as char*.  You should use std.conv.fromStringZ to convert the C char* to a D string.
[...]
> no, you don't "need" to use std conv
> 
> 
> Here is an alternative that doesn't allocate
[...]
>      // now we got a len, we can build a D string, remember, it's just a slice
> 
>      string str = cast(string) ret[0 .. len];

Slicing is exactly what std.string.fromStringz does.

<https://dlang.org/phobos/std_string.html#.fromStringz>:
"The returned array is a slice of the original buffer. The original data is not changed and not copied."
December 07, 2022
On 12/6/22 15:07, johannes wrote:

> 'write' prints out the address
> of the first byte. This is odd to me because printf does the job
> correctly.

printf behaves as what you expect because %s means dereferencing the pointer values and printing the char contents until printf sees '\0'.

> But I think to understand that write in D interpretes char*
> as a pointer to a byte.

Because it is. :) char* is nothing but a pointer to char. ('byte' exists as well, so I user 'char'.)

> it seems the
> formatting "%s" has another meaning in D ?

Yes. %s means the string representation of the variable. Sring representation of a pointer happens to be the hexadecimal representation of its value.

User-defined types can define a toSring() member function to decide how their string representation should be.

%s is the default: write(x) or writeln(x) would print like %s would.

This all works because these functions are templates, taking advantage of type deduction: They know the exact type of what they are printing. So, %s is known for that type.

printf that came from C is not templatized, so the programmer has to tell it what to do like with %s.

Ali

December 08, 2022
Thank you all for those explanations. Helps a lot!
December 08, 2022

On Tuesday, 6 December 2022 at 23:41:09 UTC, H. S. Teoh wrote:

>

On Tue, Dec 06, 2022 at 11:07:32PM +0000, johannes via Digitalmars-d-learn 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));
[...]

In D, strings are not the same as char*. You should use std.conv.fromStringZ to convert the C char* to a D string.

Well, if we think the other way around, in the example below we'd copy string, right?

void stringCopy(Chars)(string source,
                    ref Chars target)
{
  import std.algorithm : min;
  auto len = min(target.length - 1,
                 source.length);
  target[0 .. len] = source[0 .. len];
  target[len] = '\0';
}

import std.stdio;
void main()
{ //              |----- 21 chars ------| |-- 8 --|
  string sample = "bu bir deneme olmakta\0gizlimi?";
  char[30] cTxt; // 29 + 1'\0'

  sample.stringCopy = cTxt;  // disappeared ? char
  cTxt.writeln;              // appeared 28 chars

  printf("%s\n", cTxt.ptr);  // appeared 21 chars
  printf("%s\n", &cTxt[22]); // appeared 7 chars
  writefln!"%(%02X %)"(cast(ubyte[])cTxt);
}

Attention, the method used is again a slicing...

SDB@79

December 08, 2022

On Tuesday, 6 December 2022 at 23:41:09 UTC, H. S. Teoh wrote:

>

On Tue, Dec 06, 2022 at 11:07:32PM +0000, johannes via Digitalmars-d-learn 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));
[...]

In D, strings are not the same as char*. You should use std.conv.fromStringZ to convert the C char* to a D string.

Well, if we think the other way around, in the example below we'd copy string, right?

void stringCopy(Chars)(string source,
                    ref Chars target)
{
  import std.algorithm : min;
  auto len = min(target.length - 1,
                 source.length);
  target[0 .. len] = source[0 .. len];
  target[len] = '\0';
}

import std.stdio;
void main()
{ //              |----- 21 chars ------| |-- 8 --|
  string sample = "bu bir deneme olmakta\0gizlimi?";
  char[30] cTxt; // 29 + 1'\0'

  sample.stringCopy = cTxt;  // disappeared ? char
  cTxt.writeln;              // appeared 28 chars

  printf("%s\n", cTxt.ptr);  // appeared 21 chars
  printf("%s\n", &cTxt[22]); // appeared 7 chars
  writefln!"%(%02X %)"(cast(ubyte[])cTxt);
}

Attention, the method used is again a slicing...

SDB@79

« First   ‹ Prev
1 2