N. Leavy
N. Leavy

Reputation: 1074

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

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

Answers (2)

chux
chux

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

grek40
grek40

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

Related Questions