Concatenating strings into a fixed-size char array in C Using the standard C library, I want to repeatedly concatenate onto a fixed size string. Despite its promising name, the semantics of strncat do not appear suited to doing this, and the return value provides no new information. strncpy also requires a strlen to update the position. The reason I used snprintf (C99) in the code below is that it returns the number of characters written, which I can subtract from future calls. I've written a helper struct:

#include &lt;stdlib.h&gt; /* EXIT_SUCCESS */
#include &lt;stdio.h&gt;  /* *printf */
#include &lt;string.h&gt; /* strlen */

struct Supercat {
    char *print, *cursor;
    size_t left;
    int is_truncated;
};

/** Initialises {cat} to hold the string {print}, size {print_size}. It stores
 the empty string in print. */
static void supercat_init(struct Supercat *const cat, char *const print,
    const size_t print_size) {
    cat-&gt;print        = cat-&gt;cursor = print;
    cat-&gt;left         = print_size;
    cat-&gt;is_truncated = 0;
    print[0] = '\0';
}

/** Adds {append} to the string specified when \see{supercat_init} was
 called. If {append} is too big for the size, it truncates the string and sets
 {cat.is_truncated}. */
static void supercat(struct Supercat *const cat, const char *const append) {
    size_t size_took;
    int took;

    if(cat-&gt;is_truncated) return;
    took = snprintf(cat-&gt;cursor, cat-&gt;left, "%s", append);
    if(took &lt; 0)  { cat-&gt;is_truncated = -1; return; }
    if(took == 0) { return; }
    if((size_took = took) &gt;= cat-&gt;left) {
        cat-&gt;is_truncated = -1, size_took = cat-&gt;left - 1;
    }
    cat-&gt;cursor += size_took, cat-&gt;left -= size_took;
}

static const char *const start_str     = "[ ";
static const char *const end_str       = " ]";
static const char *const alter_end_str = "...]";
static const char *const sep_str       = ", ";
static const char *const null_str      = "Null";

int main(int argc, char **argv) {
    static char buffer[80];
    struct Supercat cat;
    int i;

    /* want to terminate before the end; always have space for alter_end_str */
    supercat_init(&amp;cat, buffer, sizeof buffer - strlen(alter_end_str));

    if(!argc) {
        supercat(&amp;cat, null_str);
    } else {
        supercat(&amp;cat, start_str);
        for(i = 0; i &lt; argc; i++) {
            if(i) supercat(&amp;cat, sep_str);
            supercat(&amp;cat, argv[i]);
            if(cat.is_truncated) break;
        }
        /* we are guaranteed to have enough room */
        sprintf(cat.cursor, "%s", cat.is_truncated ? alter_end_str : end_str);
    }

    printf("Arguments: %s.\n", cat.print);

    return EXIT_SUCCESS;
}


The string now will go up to 79 characters and refuse to write any more:

Thor:Supercat neil$ bin/Supercat 
Arguments: [ bin/Supercat ].
Thor:Supercat neil$ bin/Supercat saves the day!
Arguments: [ bin/Supercat, saves, the, day! ].
Thor:Supercat neil$ bin/Supercat where are you? we need you to rescue us from the burning building.
Arguments: [ bin/Supercat, where, are, you?, we, need, you, to, rescue, us, from, the,...].
Thor:Supercat neil$ bin/Supercat where are you? we need you to rescue us from this burning building.
Arguments: [ bin/Supercat, where, are, you?, we, need, you, to, rescue, us, from, this...].
Thor:Supercat neil$ bin/Supercat where are you? we need you to rescue us from danger.
Arguments: [ bin/Supercat, where, are, you?, we, need, you, to, rescue, us, from, dang...].


Using a temporary structure is a pain, but it seems to me like writing something like if(strlen(strncat(cat, argv[i], sizeof cat - strlen(cat))) &gt;= sizeof buffer) break; would be inefficient on large strings because it traverses the string 3 times (I count) to write one value. On the other hand, snprintf is overkill with a constant format string.
Back in the day, even before the official C89 standard was released, people were asking "why the hell does strcat not return the end pointer?"

I believe that the C standards committee, in a fit of pique, "doubled down" on their mistake and made a willful effort not to remedy it, simply because it would be admitting that they made a boneheaded error in the first place. It's been a sore spot ever since, and now they've switched to strcat_s which doesn't return a pointer at all, which leaves the problem unresolved, and leaves strcat as an unloved and underused function.

Also pretty much from the get-go, there were libraries of string functions that provided an alternative version of strcat that did the right thing and returned a pointer to the end of the string. Often, those functions were named strecat or strcate, with the e meaning "return a pointer to the end." There were also strecpy and various strne___ functions. (IIRC, Borland provided them with their Turbo C++ compiler.)

Considering that you know the size of the buffer, I'd suggest you simply write (or search for) a version of strnecpy and strnecat and maintain your own count of characters remaining. 

I suppose you could maintain both pointer and count in static variables, and accept a NULL destination to mean "use the previous values". You should read up on strtok first, though, to understand the implications - in general that sort of code isn't thread-safe, interrupt-safe, etc. (strtok has nothing to do with strcat but it's defined to have similar "use the last string" behavior, and usually comes with a bunch of caveats in the documentation.)

register char *p = buffer;
register char *pe = buffer + sizeof(buffer);

p = strnecpy(p, start_str, pe - p);

for (i = 0; i &lt; argc; ++i) {
    if (i) p = strnecat(p, sep_str, pe - p);
    p = strnecat(p, argv[i], pe - p);

    if (p &gt;= pe - 1) break;
}


Or, if a NULL destination means "re-use the last pointer and size info" you might have:

strnecpy(buffer, start_str, sizeof(buffer));

for (i = 0; i &lt; argc; ++i) {
    if (i) strnecat(NULL, sep_str);
    if (strnecat(NULL, argv[i]) &gt;= buffer + sizeof(buffer) - 1) break;
}


FWIW, I actually think this last bit would be a bad idea, simply because it relies so very much on hidden behavior. It's not at all clear, just reading the code, that there's a bunch of secret-squirrel things happening in the background. With a C++ string class, that's okay - with C strings, secret accounting is surprising.