Tip 12: Use asprintf to extend strings

25 October 11. [link] PDF version

Part of a series of tips on POSIX and C. Start from the tip intro page, or get 21st Century C, the book based on this series.

level: basic string user
purpose: malloc will be lonely, because you never call it

Here is an example of the basic form for appending another bit of text to a string using asprintf, which, as per tip #10, can be your workhorse for string handling:

asprintf(&q, "%s and another_clause %s", q, addme);

I (heart) this for generating queries. I would put together a chain something like this contrived example:

int row_number=3;
char *q =strdup("select ");
asprintf(&q, "%s row%i \n", q, row_number);
asprintf(&q, "%s from tab \n", q);
asprintf(&q, "%s  where row%i is not null", q, i);

And in the end I have

select row3
from tab
where row3 is not null

A rather nice way of putting together a long and painful string. I had trouble coming up with a simple example for this one that didn't look contrived. But when each clause of the query requires a subfunction to write by itself, this sort of extend-the-query form starts to make a lot of sense. Apophenia users, see also apop_text_paste.

But it's a memory leak, because the blob at the original address of q isn't released when q is given a new location by asprintf. For one-off string generation, it's not even worth caring about--you can drop a few million query-length strings on the floor before anything noticeable happens.

If you are in a situation where you might produce an unknown number of strings of unknown length, then you will need a form like this:

//Safe asprintf macro
#define Sasprintf(write_to,  ...) {\
    char *tmp_string_for_extend = write_to;    \
    asprintf(&(write_to), __VA_ARGS__);   \
    free(tmp_string_for_extend);  \
}

//sample usage:
int main(){
    int i=3;
    char *q = NULL;
    Sasprintf(q, "select * from tab");
    Sasprintf(q, "%s where row%i is not null", q, i);
    printf("%s\n", q);
}

Discussion and caveats:
The Sasprintf macro, plus occasional use of strdup, is enough for roughly 100% of your string-handling needs. Except for one glitch and the occasional free, you don't have to think about memory issues at all.

The glitch is that if you forget to initialize q to NULL or via strdup then the first use of the Sasprintf macro will be freeing whatever junk happened to be in the uninitialized location q--a segfault.

As you learned in the last tip, the following also fails--wrap that declaration in strdup to make it work:

char *q = "select * from";
Sasprintf(q, "%s %s where row%i is not null", q, tablename, i);


[Previous entry: "Tip 11: String literals"]
[Next entry: "Tip 13: Use a debugger"]