digitalmars.D.learn - Formatting a string on a variadic parameter function without using GC
- David G. Maziero (40/40) Mar 01 2016 Hi. I think it's the first time I post here.
- Mike Parker (27/42) Mar 01 2016 You're asking for trouble here. There's no guarantee that any D
- Mike Parker (3/4) Mar 01 2016 Ugh. And the proper declaration in D:
- David G. Maziero (7/7) Mar 01 2016 Yes, I'm aware of the null-termination thing. I might have pasted
- David G. Maziero (4/4) Mar 01 2016 I forgot to add that the "RenderText(font,0,0,"FPS:
- David G. Maziero (36/36) Mar 01 2016 I figured out what I wanted. Thanks for your answer Mike, sorry
- David G. Maziero (3/8) Mar 01 2016 Sorry again, where it reads "sprintf" should be "vsprintf".
- David G. Maziero (5/7) Mar 02 2016 Just one more correction for future reference, RenderText should
- cym13 (5/8) Mar 02 2016 Even null-terminated be sure to profile your loop, for isn't
- Luis (4/35) Mar 02 2016 Read https://dlang.org/spec/arrays.html#strings and try to use
- Mike Parker (3/6) Mar 02 2016 Yes, but that's not what you use when you want to avoid
Hi. I think it's the first time I post here. I've been flirting with D for some time, but now I decided to start a little project. Consider the following function: void RenderText( FontBMP font, int x, int y, const char* text ) { SDL_Rect rect1, rect2; rect2.x = x; rect2.y = y; rect2.w = font.width; rect2.h = font.height; rect1.w = font.width; rect1.h = font.height; for( int r=0; text[r]!='\0'; ++r ) { char letter = text[r]; rect1.x = font.width*(letter%font.horizontal_count); rect1.y = font.height*(letter/font.horizontal_count); if( letter>=33 ) SDL_RenderCopy( g_renderer, font.texture, &rect1, &rect2 ); rect2.x += font.spacing_x; if( letter==10 ) { rect2.x = x; rect2.y += font.spacing_y; } } } And the use code: char[256] text; sprintf( &text[0], "Player: pos:%.3f - speed:%.3f", player.position, player.speed ); RenderText( font, 0, 0, cast(char*)text ); I could simply do "RenderText(font,0,0,"FPS: "~to!string(fps));" but I want to avoid GC entirely. The ideal scenario is to have something like "void RenderText( /*etc*/, const char* fmt, ... )" but I can't seem to figure a way to pass the variadic parameters to sprintf. Any ideas? Thanks in advance.
Mar 01 2016
On Wednesday, 2 March 2016 at 01:39:13 UTC, David G. Maziero wrote:Consider the following function: void RenderText( FontBMP font, int x, int y, const char* text ) {for( int r=0; text[r]!='\0'; ++r ) {You're asking for trouble here. There's no guarantee that any D string is going to be null terminated. String literals are, but beyond that, all bets are off.char[256] text; sprintf( &text[0], "Player: pos:%.3f - speed:%.3f", player.position, player.speed );Instead of sprintf, look into using std.format.sformat [1] (see below). It's equivalent to std.format.format, but allows you to provide a buffer.RenderText( font, 0, 0, cast(char*)text );It's considered bad form to cast an array to a pointer like this. If you need a pointer to an array, just use the ptr property: text.ptr. Always be aware of the null terminator situation, though, when passing to C.I could simply do "RenderText(font,0,0,"FPS: "~to!string(fps));"Won't compile without using the .ptr property (or casting, which again, you shouldn't do). Moreover, you run into the null terminator problem. "FPS :" will be null terminated because it's a literal, but the string produced by concatentating it with the result of to!string would not be.but I want to avoid GC entirely. The ideal scenario is to have something like "void RenderText( /*etc*/, const char* fmt, ... )" but I can't seem to figure a way to pass the variadic parameters to sprintf. Any ideas?With sformat, something like this (not tested): import std.format; char buf[1024]; auto fmt = sformat(buf, "Foo: %s", 42); if(fmt.length < buf.length) buf[fmt.length] = 0; else buf[$-1] = 0; RenderText(blah, blah, buf.ptr); sformat returns a slice of the buffer containing the formatted string. You can use its length to guarantee the buffer is null terminated.
Mar 01 2016
On Wednesday, 2 March 2016 at 04:12:13 UTC, Mike Parker wrote:char buf[1024];Ugh. And the proper declaration in D: char[1024] buf;
Mar 01 2016
Yes, I'm aware of the null-termination thing. I might have pasted code that I already changed. But I already messed with sformat, and it seems that it does use the GC. I've put nogc in RenderText, and the compiler says sformat uses GC, so I don't know. But the thing is, I don't want to build the string before calling RenderText, I want RenderText to do it.
Mar 01 2016
I forgot to add that the "RenderText(font,0,0,"FPS: "~to!string(fps));" was an older version where it wasn't const char *, but string. And I was using a foreach, so no null-termination. But that's beyond the point of not using GC.
Mar 01 2016
I figured out what I wanted. Thanks for your answer Mike, sorry to bother you though. Here's the result: void RenderText( FontBMP font, int x, int y, const char* text, ... ) { SDL_Rect rect1, rect2; rect2.x = x; rect2.y = y; rect2.w = font.width; rect2.h = font.height; rect1.w = font.width; rect1.h = font.height; char[256] buff; va_list ap; va_start( ap, text ); sprintf( buff.ptr, text, ap ); va_end( ap ); for( int r=0; buff[r]!='\0'; ++r ) { char letter = buff[r]; rect1.x = font.width*(letter%font.horizontal_count); rect1.y = font.height*(letter/font.horizontal_count); if( letter>=33 ) SDL_RenderCopy( g_renderer, font.texture, &rect1, &rect2 ); rect2.x += font.spacing_x; if( letter==10 ) { rect2.x = x; rect2.y += font.spacing_y; } } } Since the "string" is built by sprintf, it'll be null-terminated. Sorry for posting without doing more research. I didn't realise I could use va_start/va_end just like in C.
Mar 01 2016
On Wednesday, 2 March 2016 at 05:04:37 UTC, David G. Maziero wrote:char[256] buff; va_list ap; va_start( ap, text ); sprintf( buff.ptr, text, ap ); va_end( ap );Sorry again, where it reads "sprintf" should be "vsprintf".
Mar 01 2016
On Wednesday, 2 March 2016 at 05:04:37 UTC, David G. Maziero wrote:void RenderText( FontBMP font, int x, int y, const char* text,Just one more correction for future reference, RenderText should beextern(C) void RenderText...in order for it to work correctly with va_start/etc.
Mar 02 2016
On Wednesday, 2 March 2016 at 05:04:37 UTC, David G. Maziero wrote:I figured out what I wanted. Thanks for your answer Mike, sorry to bother you though. [...]Even null-terminated be sure to profile your loop, for isn't garanteed to be faster than foreach at all and foreach is definitely safer.
Mar 02 2016
On Wednesday, 2 March 2016 at 04:12:13 UTC, Mike Parker wrote:On Wednesday, 2 March 2016 at 01:39:13 UTC, David G. Maziero wrote:Read https://dlang.org/spec/arrays.html#strings and try to use std.string.toStringzConsider the following function: void RenderText( FontBMP font, int x, int y, const char* text ) {for( int r=0; text[r]!='\0'; ++r ) {You're asking for trouble here. There's no guarantee that any D string is going to be null terminated. String literals are, but beyond that, all bets are off.char[256] text; sprintf( &text[0], "Player: pos:%.3f - speed:%.3f", player.position, player.speed );Instead of sprintf, look into using std.format.sformat [1] (see below). It's equivalent to std.format.format, but allows you to provide a buffer.RenderText( font, 0, 0, cast(char*)text );It's considered bad form to cast an array to a pointer like this. If you need a pointer to an array, just use the ptr property: text.ptr. Always be aware of the null terminator situation, though, when passing to C.I could simply do "RenderText(font,0,0,"FPS: "~to!string(fps));"Won't compile without using the .ptr property (or casting, which again, you shouldn't do). Moreover, you run into the null terminator problem. "FPS :" will be null terminated because it's a literal, but the string produced by concatentating it with the result of to!string would not be.
Mar 02 2016
On Wednesday, 2 March 2016 at 10:57:35 UTC, Luis wrote:Read https://dlang.org/spec/arrays.html#strings and try to use std.string.toStringzYes, but that's not what you use when you want to avoid allocation.
Mar 02 2016