debugcMinor
Concatenating strings into a fixed-size char array in C
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
```
#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
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
Also pretty much from the get-go, there were libraries of string functions that provided an alternative version of
Considering that you know the size of the buffer, I'd suggest you simply write (or search for) a version of
I suppose you could maintain both pointer and count in static variables, and accept a
Or, if a NULL destination means "re-use the last pointer and size info" you might have:
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.
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.