Home > Blockchain >  Dynamically allocate `char*` with concatention and format literals?
Dynamically allocate `char*` with concatention and format literals?

Time:03-24

With support for macOS, Windows (MSVC), and Linux, how do I do the following?

char *s;
func(&s, "foo");
if (<condition>) func(&s, "bar%s", "can")
/* want "foobarcan", and I don't know `strlen(s)` AoT */

I've tried with asprintf (was able to find an MSVC implementation) but that didn't seem to work well on this kind of workflow. fopencookie and funopen seem convenient but unavailable on MSVC.

Maybe there's some clean way with realloc to create a NUL ended char* in C?

CodePudding user response:

As pointed out in the comments, (v)snprintf always returns the number of bytes that would have been written (excluding the null terminating byte), even if truncated. This has the effect that providing the function with a size argument of 0 returns the length of the to-be-formatted string.

Using this value, plus the string length of our existing string (if applicable), plus one, we (re)allocate the appropriate amount of memory.

To concatenate, simply print the formatted string at the correct offset.

An example, sans error checking.

#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char *dstr(char **unto, const char *fmt, ...) {
    va_list args;
    size_t base_length = unto && *unto ? strlen(*unto) : 0;

    va_start(args, fmt);
                /* check length for failure */
    int length = vsnprintf(NULL, 0, fmt, args);
    va_end(args);

                /* check result for failure */
    char *result = realloc(unto ? *unto : NULL, base_length   length   1);

    va_start(args, fmt);
                /* check for failure*/
    vsprintf(result   base_length, fmt, args);
    va_end(args);

    if (unto)
        *unto = result;

    return result;
}

int main(void) {
    char *s = dstr(NULL, "foo");

    dstr(&s, "bar%s%d", "can", 7);

    printf("[[%s]]\n", s);

    free(s);
}

stdout:

[[foobarcan7]]

The caveat here is that you can not write:

char *s;
dstr(&s, "foo");

s must be initialized as NULL, or the function must be used directly as an initializer, with the first argument set to NULL.

That, and the second argument is always treated as a format string. Use other means of preallocating the first string if it contains unsanitary data.

Example exploit:

/* exploit */
char buf[128];
fgets(buf, sizeof buf, stdin);

char *str = dstr(NULL, buf);
puts(str);
free(str);

stdin:

%d%s%s%s%s%d%p%dpapdpasd%d%.2f%p%d

Result: Undefined Behavior

  • Related