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 <stdlib.h> /* EXIT_SUCCESS */
#include <stdio.h> /* *printf */
#include <string.h> /* 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 < 0) { cat->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 alter_end_str */
supercat_init(&cat, buffer, sizeof buffer - strlen(alter_end_str));
if(!argc) {
supercat(&cat, null_str);
} else {
supercat(&cat, start_str);
for(i = 0; i < argc; i++) {
if(i) supercat(&cat, sep_str);
supercat(&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))) >= 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.