Reputation: 1074
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?
Upvotes: 3
Views: 454
Reputation: 153348
Perhaps the following. Allocate the memory with an aligned VLA and then overlay.
typedef struct strbuf_t_ {
int length;
char str[];
} strbuf_t;
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdalign.h>
int main(void) {
char *s = "Hello";
size_t length = strlen(s);
size_t n = sizeof (strbuf_t) + length + 1;
_Alignas(strbuf_t) unsigned char mem[n];
strbuf_t *xx = (strbuf_t*) mem;
xx->length = length;
memcpy(xx->str, s, n+1);
printf("int:%zu s:%zu n:%zu mem:%zu\n",
sizeof xx->length, sizeof (strbuf_t), n, sizeof mem);
return 0;
}
Output
int:4 s:4 n:6 mem:10
Note: C99 allows the last struct
member to have an indefinite array count of []
Upvotes: 1
Reputation: 13438
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);
}
Upvotes: 1