C-API: DStrings
Not logged in


A DString is a string that uses malloc only for longer strings:
Jsi_DString d = {"Here is your score "};
puts(Jsi_DSPrintf(&d, "%s: -> %d/%d", user, n, m));

The above allocates the string on the stack, appends to it, and outputs it, all without using malloc.

If a string exceeds 200 characters a malloc will be used, which then Jsi_DSFree discards.

Space can be also be reused with Jsi_DSSetLength, as in:

Jsi_DString d = {};
Jsi_DSPrintf(&d , "%0300d", 1); // Malloc
Jsi_DSSetLength(&d, 0);
Jsi_DSPrintf(&d , "%0300d", 1); // No-malloc
Jsi_DSPrintf(&d , "%0300d", 1); // Malloc

Note for time critical code, it is marginally more efficient to use Jsi_DSInit():

Jsi_DString d;
Jsi_DSAppend(&d, "Some stuff", NULL);
Jsi_DSAppendLen(&d, "!", 1);

Function Summary

Here are the function signatures:

char*   Jsi_DSAppend(Jsi_DString *dsPtr, const char *str, ...);
char*   Jsi_DSAppendLen(Jsi_DString *dsPtr, const char *bytes, int length); 
void    Jsi_DSFree(Jsi_DString *dsPtr);
char*   Jsi_DSFreeDup(Jsi_DString *dsPtr); 
void    Jsi_DSInit(Jsi_DString *dsPtr); 
int     Jsi_DSLength(Jsi_DString *dsPtr);
char*   Jsi_DSPrintf(Jsi_DString *dsPtr, const char *fmt, ...);
char*   Jsi_DSSet(Jsi_DString *dsPtr, const char *str); 
int     Jsi_DSSetLength(Jsi_DString *dsPtr, int length);
char*   Jsi_DSValue(Jsi_DString *dsPtr);
#define JSI_DSTRING_VAR(varPtr,size) //...

and the details:

Jsi_DSAppendAppend one or more string arguments (plus NULL sentinal).
Jsi_DSAppendLenAppend a string of given length (or -1 for strlen).
Jsi_DSFreeRelease allocated memory and sets variable back to re-initialized/empty.
Jsi_DSFreeDupReturn malloced string, then calls Jsi_DSFree.
Jsi_DSInitInitialize the variable, ignoring current data therein.
Jsi_DSLengthReturn the length.
Jsi_DSPrintfFormat output and append to DString. Returns string from the current printf.
Jsi_DSSetSame as Jsi_DSSetLength(dsPtr,0) plus Jsi_AppendLen.
Jsi_DSSetLengthIf < current length truncates string. Else sets min allocated space. Return allocated size.
Jsi_DSValueReturn string value.
JSI_DSTRING_VARMacro that declares a large DString on the stack.

Functions Details

The following contains detailed descriptions of the DString functions.


char* Jsi_DSAppend(Jsi_DString *dsPtr, const char *str, ...)
Calls Jsi_DSAppendLen for each string value argument, passing in -1 for the length. Each string is assumed to be null terminated and the final argument must be a NULL.
RETURNS: The string starting at the first appended character.


char* Jsi_DSAppendLen(Jsi_DString *dsPtr, const char *bytes, int length)
Append length bytes to the DString. If length is < 0, the value of strlen is used. If required, the DString is realloced to be large enough to contain bytes, plus an extra null byte that is added to the end.
RETURNS: The string starting at the first appended character.


void Jsi_DSFree(Jsi_DString *dsPtr)
Frees any allocated space and sets the DString back to empty such that it is safe to exit the scope. Or the DString may be reused (also see Jsi_DSSetLength).


char* Jsi_DSFreeDup(Jsi_DString *dsPtr)
Returns the malloced string value and resets the DString in the same way as Jsi_DSFree. This just avoids the user having to do an extra malloc/free if the DString was already malloced. It is then the responsibility of the caller to free the returned value.
RETURNS: The string that was contained in the DString.


void Jsi_DSInit(Jsi_DString *dsPtr)
Initialize a DString.


int Jsi_DSLength(Jsi_DString *dsPtr)
RETURNS:The string length dsPtr->len.


char* Jsi_DSPrintf(Jsi_DString *dsPtr, const char *fmt, ...)
Perform printf style string formatting as directed by the fmt string. Under the covers, this utilizes vsnprintf.
RETURNS: The string starting at the first appended character.


char* Jsi_DSSet(Jsi_DString *dsPtr, const char *str)
Same as calling Jsi_DSSetLength(dsPtr,0) followed by Jsi_DSAppendLen(dsPtr,str). Sets the DString to str without freeing any allocated space. But note that it is not safe to exit the scope without first calling Jsi_DSFree.


int Jsi_DSSetLength(Jsi_DString *dsPtr, int length)
Depending on dsPtr->len, truncates a string or sets the minimum allocated space.
Note: Does not set dsPtr->len unless truncating. Also an extra byte is always added to the allocation, but this is not reported in the allocated length.
RETURNS: The currently allocated size. ie. the size of the maximum string that will fit without a call to realloc.


char* Jsi_DSValue(Jsi_DString *dsPtr)
Gets the current string value.
RETURNS: The string dsPtr->str.


#define JSI_DSTRING_VAR(varPtr,size) //...
Declares a DString struct and pointer in the current stack frame. See the next section on Large Strings.

Large String Buffers

When working with larger strings, we may want to preallocate a large string in order to avoid repeated calls to realloc() as the string grows. The normal approach might be something like:

Jsi_DString dStr;
Jsi_DSSetLength(&dStr, 50000);

Another alternative is to use the JSI_DSTRING_VAR macro, which avoids using malloc entirely. JSI_DSTRING_VAR efficiently declares a Jsi_DString* pointing to an enlarged static DString upon the stack: eg:

JSI_DSTRING_VAR(dsPtr, 50000);
Jsi_DSPrintf(dsPtr, "%04999d", 1); // No malloc.

Compared With C++

Consider C++ stringstream, which provides convenient dynamic string support with type safety.

std::stringstream str;
str << "ABC " << 123;

The tradeoffs of stringstream are:

DString provides familiar printf style formatting:

Jsi_DString dstr = {};
puts(Jsi_DSPrintf(&dstr, "ABC %d", 123));

Printf style modifiers are simpler than stringstream:

Jsi_DSPrintf(&dstr, "%02d%-3d%04d", v1, v2, v3);
str << std::setfill('0') << std::setw(2) << v1 
    << std::setfill(' ') << std::setw(3) << std::left  << v2
    << std::setfill('0') << std::setw(4) << std::right << v3;


The gcc compiler makes DString usage quite safe.

There however are some gotchas to be aware of: