hotspur
Thanks for the reply Casey!
I'm happy to have fixed sizes on all of the runtime-generated strings (it's what I've been doing so far). There are some user-input strings but they can all be assigned arbitrary max lengths. My biggest pain point is that my code is littered with char Buffer[N] where N is varying all the time, so N is either a magic number or it needs a variable name that needs to be passed to any safe string function.
If you actually have an array buffer (rather than a pointer):
| #define array_count(a) (sizeof(a)/sizeof((a)[0]))
string_t s = MakeString(buffer, array_count(buffer));
|
Then you can resize your buffer as you like, and the size handling will just work. However, if you switch to a pointer at any point, this breaks (rather horribly, because it will still compile and just give you the wrong size).
C++ templates can actually solve the problem as well:
| template<size_t N>
size_t ArrayCount(char buffer[N]) {
return N;
}
|
This has all the unfortunate effects of templates in general, but if you're using a small number of translation units like HH, the compile overhead isn't actually that bad.
I have another idea... but I've never seen it before:
| #define FixedStringLengthMax 1024
struct fixed_string
{
int32 Length;
char Buffer[FixedStringLengthMax];
};
|
Assuming this is large enough for my runtime string needs, it appears quite convenient.
Now I don't have to pass the capacity to create or append to these since its always the same... awesome!
Except now I'm afraid to blow up the stack. Each string is now 1KB, which seems fine if used judiciously... but I have no idea to what extent stack sizes vary given hardware, OS, etc.
Let's assume you have just a general string type, size, length, and pointer, like this:
| struct string_t {
u64 size;
u64 length;
char * buffer;
};
|
The process of wrapping a stack buffer in this struct is straightforward, but long-winded:
| char foo_buffer[512];
string_t foo;
foo.size = array_count(foo_buffer);
foo.length = 0;
foo.buffer = foo_buffer;
|
Well, you can just wrap that in a macro:
| #define STACK_STRING(name, count) STACK_STRING_BUFFER(name)[count];\
string_t name;\
name.size = count;\
name.length = 0;\
name.buffer = STACK_STRING_BUFFER(name);
//...
{
STACK_STRING(foo, 512);
// 'foo' is now a string with a 512 character buffer.
}
|
The 'STACK_STRING_BUFFER()' macro just needs to generate a unique name for the buffer.
You want to do this, or something similar, because it makes it a lot easier to do things like strcpy and sprintf properly. (You wrap those calls inside functions dealing with string_t, and then you only ever have to deal with the details in one place.) However, you do need to take care about never calling free() in your string functions in this case (so, no reallocations), but this is an easy enough thing to handle.