C function that emulates string concatenation operator '+' in Python or Go I'm working on a project of mine which required multiple calls to strncat(), and it came to me that it'd have been much easier if C also had something like &quot;file://&quot; + getenv(&quot;HOME&quot;) + filename for string generation. So I built a function that works in a similar fashion, it is a variadic function, the arguments are NULL terminated.
Usage: char *str = mutant_string (&quot;file://&quot;, getenv (&quot;HOME&quot;), filename, NULL);
It's obviously up to the user to free the memory str allocated.
The code:-
char *mutant_string (const char *s, ...) {

    va_list argv;
    va_start (argv, s);

    char *return_string = strdup (s);

    size_t realsize, nmemb;

    realsize = strlen (return_string); // Or initial size

    char *c, *ptr;

    while ((c = va_arg(argv, char*)))
    {
        nmemb = strlen (c);
        realsize += nmemb;

        ptr = realloc (return_string, sizeof(char) * (realsize + 1));

        if (!ptr) {
            perror (&quot;realloc&quot;);
            free (return_string);
            va_end (argv);
            return NULL;
        }

        return_string = ptr;
        strncat (return_string, c, nmemb);
    }

    return_string[realsize] = 0;

    va_end (argv);

    return return_string;
}

I welcome any kind of observations or opinions. Let me know if there's an external library or function that does exactly this and something that I can read.
Thank you.
It looks like this code needs a couple of headers to be included before it will compile:
#include &lt;stdarg.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;string.h&gt;

It also only works on platforms that have strdup().  It would be easy to make it more portable.
If s is a null pointer, then we have undefined behaviour, but we haven't indicated that we don't allow a null for the first argument.
Why are we not testing whether strdup() returns a null pointer?
Well done for avoiding the common hazard with realloc().  Too many C programmers fail to deal properly with allocation failure.  The scope of ptr could be reduced so that we declare and initialise together, and we don't need to multiply by sizeof (char) (which must be 1, by definition):
char *ptr = realloc(return_string, realsize + 1);

The use of strncat() in a loop is wasteful, since it starts from the beginning every time.  Since we know where the string ends, it's better to start from that position.
The algorithm could be much more efficient.  Instead of many realloc() calls, we could make two passes over the arguments: one to measure the total size, and one to copy the characters across.
Compare this extract from a Valgrind run of the code:
==25527== HEAP SUMMARY:
==25527==     in use at exit: 0 bytes in 0 blocks
==25527==   total heap usage: 5 allocs, 5 frees, 1,063 bytes allocated

against the two-pass version that doesn't need to realloc:
==24899== HEAP SUMMARY:
==24899==     in use at exit: 0 bytes in 0 blocks
==24899==   total heap usage: 2 allocs, 2 frees, 1,037 bytes allocated

That's fewer than half the allocations for a very simple &quot;Hello World&quot; test program:
char *mutant_string (const char *s, ...);

#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;

int main(void)
{
    char *s = mutant_string(&quot;Hello&quot;, &quot; &quot;, &quot;World&quot;, &quot;!&quot;, NULL);
    if (!s) {
        return 1;
    }
    puts(s);
    free(s);
}

Here's what the two-pass version looks like:
#include &lt;stdarg.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;string.h&gt;

char *mutant_string(const char *s, ...)
{
    if (!s) {
        return NULL;
        /* alternatively - return a new, empty string */
    }

    va_list argv;

    /* First pass - measure the total length */
    size_t total_len = strlen(s);
    va_start(argv, s);
    const char *c;
    while ((c = va_arg(argv, const char*))) {
        total_len += strlen(c);
    }
    va_end(argv);

    char *buf = malloc(total_len + 1);
    if (!buf) {
        return buf;
    }

    /* Second pass - copy the string data */
    char *p = buf;
    {
        /* first argument */
        size_t len = strlen(s);
        memcpy(p, s, len);
        p += len;
    }

    /* other arguments */
    va_start(argv, s);
    while ((c = va_arg(argv, const char*))) {
        size_t len = strlen(c);
        memcpy(p, c, len);
        p += len;
    }
    va_end(argv);
    *p = '\0';

    return buf;
}