/ C-API: DStrings


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

The strings above are short so the do not use malloc.

If a string exceeds 200 characters a malloc will be used,

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

Space is discared with Jsi_DSFree, and can be reused with Jsi_DSSetLength:

You can also 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); 
uint     Jsi_DSLength(Jsi_DString *dsPtr);
char*   Jsi_DSPrintf(Jsi_DString *dsPtr, const char *fmt, ...);
char*   Jsi_DSSet(Jsi_DString *dsPtr, const char *str); 
uint     Jsi_DSSetLength(Jsi_DString *dsPtr, uint 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.


uint 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.


uint Jsi_DSSetLength(Jsi_DString *dsPtr, uint length)
Depending on dsPtr->len, truncates a string or sets the minimum allocated space.
  • If length is < 0, does nothing and just returns the current size allocation.
  • if length is < current length, the string is truncated.
  • Otherwise, enforces the allocated space is at least length.
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:

  • Implicit memory allocation.
  • Implicit code execution.
  • Hard to inspect in gdb.
  • Awkward to obtain the C string value.
  • Not available in plain C-code.

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.

  • It generates warnings for Jsi_DSPrintf argument mismatches.
  • It warns when Jsi_DSAppend is missing NULL terminator.

There however are some gotchas to be aware of:

  • DStrings greater than 200 bytes can not be assigned to one another.
  • Failing to call Jsi_DSFree can leak memory.
  • A DString has a maximum (compile time limit) of a 100 Meg.