What is a good way to allocate a string size buffer on the stack?

advertisements

Using pure C (and the C-preprocessor) I would like to create a string buffer that includes a length parameter, so that I could pass it around easily and any functions that operate on it could do so without danger of writing past the end. This is simple enough:

typedef struct strbuf_t_ {
    int  length;
    char str[1];
} strbuf_t;

but then if I want a small scratch space on the stack to format some output text, there is no trivial way to allocate a strbuf_t on the stack. What I would like is some clever macro that allows me to do:

STRBUF(10) foo;
printf("foo.length = %d\n", foo.length); // outputs: foo.length = 10
strncpy(foo.str, "this is too long", foo.length);

Unfortunately I don't seem to be able to do that. The best I've come up with is:

#define STRBUF(size, name) \
    struct {\
        strbuf_t buf;\
        char space[size - 1];\
        char zero;\
    } name ## _plus_space_ = { .buf={.length=size}, .space="", .zero='\0'};\
    strbuf_t *name = &name ## _plus_space_.buf

int main(void)
{
    STRBUF(10, a);

    strncpy(a->str, "Hello, world!", a->length);
    printf("a->length = %d\n", a->length); // outputs: a->length = 10
    puts(a->str); // outputs: Hello, wor
}

This meets all of the requirements I listed, but a is a pointer not the structure itself, and the allocation is certainly not intuitive.

Has anyone come up with something better?


I think you are already pretty close to a solution. Just keep a char* in your struct and allocate it via char-array. In order to have the save trailing zero at the end of string, just allocate an extra char additional to the size and initialize the whole array with zeroes.

typedef struct
{
    int length;
    char* str;
} strbuf_t;

#define STRBUF(varname, size) \
    char _buffer_ ## varname[size + 1] = {'\0'}; \
    strbuf_t varname = { size, _buffer_ ## varname }

int main()
{
    STRBUF(a, 10);

    strncpy(a.str, "Hello, world!", a.length);
    printf("a.length = %d\n", a.length);
    puts(a.str);
}