HiveBrain v1.2.0
Get Started
← Back to all entries
debugcMinor

Concatenating strings into a fixed-size char array in C

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
arrayintoconcatenatingsizecharfixedstrings

Problem

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 / EXIT_SUCCESS /
#include / printf */
#include / 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->print = cat->cursor = print;
cat->left = print_size;
cat->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->is_truncated) return;
took = snprintf(cat->cursor, cat->left, "%s", append);
if(took is_truncated = -1; return; }
if(took == 0) { return; }
if((size_took = took) >= cat->left) {
cat->is_truncated = -1, size_took = cat->left - 1;
}
cat->cursor += size_took, cat->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

Solution

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

Code Snippets

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

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

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

    if (p >= pe - 1) break;
}
strnecpy(buffer, start_str, sizeof(buffer));

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

Context

StackExchange Code Review Q#155132, answer score: 3

Revisions (0)

No revisions yet.