Jérôme Pouiller
Jérôme Pouiller

Reputation: 10197

How to allocate a struct with a flexible array member on the stack

Let's say we have a struct ending with a flexible array member:

struct foo {
    size_t len;
    uint8_t data[];
};

How to allocate this struct on the stack (ie. memory is automatically released at the end of the scope)? In add, it would be nice if len could contain the size of the field data.

Currently, I do things like:

uint8_t buf[256];
struct foo *foo = (struct foo *)buf;
foo->len = sizeof(buf) - sizeof(struct foo);

However, it is error prone. Use of alloca() may be slightly better:

struct foo *foo = alloca(256 + sizeof(struct foo));
foo->len = 256;

From there, I could define a macro like this:

#define STACK_ALLOC_FOO(SIZE) ({                          \
    struct foo *_tmp = alloca(SIZE + sizeof(struct foo)); \
    _tmp->len = SIZE;                                     \
    _tmp;                                                 \
})

And declare it with:

struct foo *foo = STACK_ALLOC_FOO(256);

However, I am not sure of lifetime of the memory allocated with alloca(). Is it the inner scope or the function?

In add, it does not work to allocate a global variable (even if it is not my main concern).

Does someone has good practices in mind to allocate structures with flexible array members on stack?

Upvotes: 9

Views: 1529

Answers (5)

Jérôme Pouiller
Jérôme Pouiller

Reputation: 10197

As alternative, I suggest that:

#define DECLARE_FOO(NAME, SIZE) \
  struct {                      \
      struct foo __foo;         \
      char __data[SIZE];        \
  }  __ ## NAME = {             \
      .__foo.len = SIZE,        \
  };                            \
  struct foo *NAME = &(__ ## NAME).__foo;

So you can do:

DECLARE_FOO(var, 100);

It is not very elegant. However, it works to declare global/static variables and it does not rely on any cast operator.

Upvotes: 1

Lundin
Lundin

Reputation: 213563

Let's say we have a struct ending with a Variable Length Array (VLA):

Well, you don't. You have a struct ending with a flexible array member. Different thing and mainly used for dynamic memory allocation scenarios.

How to allocate this struct on the stack

It's pretty hard to do that without some non-standard extension. For example an alloca extension that guarantees to return memory which does not have an effective type. Meaning that the memory has not yet been marked internally by the compiler to have a certain type. Otherwise...

struct foo *foo = (struct foo *)buf;

You get strict aliasing violation undefined behavior, like in the above buggy code. What is the strict aliasing rule?

Additionally, you also need to take care of alignment & padding.

However, I am not sure of lifetime of the memory allocated with alloca(). Is it the inner scope or the function?

Yeah probably. It's not a standard function and I'm not sure any lib gives a portable guarantee of its behavior. It's not even a POSIX function. Linux man gives the guarantee that:

The alloca() function allocates size bytes of space in the stack frame of the caller. This temporary space is automatically freed when the function that called alloca() returns to its caller.

I'm assuming this holds for gcc/glibc under *nix, but not other tool chains or systems.


What you could do instead, to get portable and rugged code, is something like this:

struct foo {
    size_t len;
    uint8_t data[];
};

struct bar256 {
  size_t len;
  uint8_t data[256];
};

typedef union
{
  struct foo f;
  struct bar256 b;
} foobar256;

Here bar256 and foobar256 could be defined locally. You can access the data either through the f.data or b.data of a foobar256. This kind of type punning is allowed and well-defined in C.

At this point you might realize that the struct is just more trouble that it's worth and just go with two local variables, one being an actual VLA:

size_t len = ... ;
uint8_t data[len];

Upvotes: 7

Ahmed Masud
Ahmed Masud

Reputation: 22374

If your intent is to use it like this:

#include <stdio.h>
#include <stdlib.h>
#include <alloca.h>
#include <string.h>
#include <stdint.h>
#include <sys/types.h>

struct foo {
    size_t len;
    uint8_t data[];
}; 


#define STACK_ALLOC_FOO(SIZE) ({                          \
    struct foo *_tmp = alloca(SIZE + sizeof(struct foo)); \
    _tmp->len = SIZE;                                     \
    _tmp;                                                 \
})


void print_foo() {
    struct foo *h = STACK_ALLOC_FOO(sizeof("Hello World"));
    memcpy(h->data, "Hello World", h->len);
    fprintf(stderr, "[%lu]%s\n", h->len, h->data);
}

int main(int argc, char *argv[])
{
    print_foo();
    return 0;
}

Because of this:

The space allocated by alloca() is not automatically deallocated if the pointer that refers to it simply goes out of scope.

it will produce perfectly valid code because the only thing that is going out of scope is the *_tmp and that does NOT deallocate the alloca, you are still within the same stack-frame. it DOES get de-allocated with the return from print_foo.

Actually it's very interesting to see how the compiler deals with the optimization flags and the assembly output. (The alloca related code is fully duplicated in main if you e.g. use -O3)

Hopefully that helps

Upvotes: 0

KamilCuk
KamilCuk

Reputation: 140960

How to allocate a struct with a Variable Length Array (VLA) on the stack

You have to make sure your buffer is properly aligned. Use unsinged char or just char to represent "bytes", uint8_t represents an 8-bit number.

#include <stdalign.h>
alignas(struct foo) unsigned char buf[sizeof(struct foo) + 20 * sizeof(uint8_t));
struct foo *foo = (struct foo *)buf;
foo->len = sizeof(buf) - sizeof(struct foo);

I could define a macro like this:

The ({ is a gcc extension. You could also define a macro to define the variable, like:

// technically UB I believe
#define FOO_DATA_SIZE  sizeof(((struct foo*)0)->data)

struct foo *foo_init(void *buf, size_t bufsize, size_t count) {
    struct foo *t = buf;
    memset(t, 0, bufsize);
    t->size = count;
    return t;
}

#define DEF_struct_foo_pnt(NAME, COUNT) \
    _Alignas(struct foo) unsigned char _foo_##NAME##_buf[sizeof(struct foo) + COUNT * FOO_DATA_SIZE); \
    struct foo *NAME = foo_init(_foo_##NAME##_buf, sizeof(buf), COUNT);

void func() {
   DEF_struct_foo_pnt(foo, 20);
}

Use of alloca() may be slightly better:

Unless you happen to call alloca() in a loop...

I am not sure of lifetime of the memory allocated with alloca(). Is it the inner scope or the function?

Memory allocated with alloca gets freed at end of function or at end of scope?

it does not work to allocate a global variable (even if it is not my main concern).

It will be hard - C does not have constructors. You could use an external tool or experiment with preprocessor magic to generate code, like:

_Alignas(struct foo) unsigned char buf[sizeof(struct foo) + count * sizeof(uint8_t)) = {
     // Big endian with 64-bit size_t?
     20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
struct foo *foo_at_file_scope = (struct foo*)buf;

i.e. you have to initialize the buffer, not the struct. I think I would write a tool in C using the same compiler with same options to generate code for that (for cross-compiling in gcc-ish environment, I would only compile some executable with initialization to an ELF file, and instead of running it, get the initialization from ELF file with objdump, and the process it to generate C source).

Alternatively, you could (ab-)use GCC extension __attrbute__((__constructor__)) - define a function with that attribute in another macro. Something along:

#define DEF_FILE_SCOPE_struct_foo_pnt(NAME, COUNT) \
    _Alignas(struct foo) unsigned char _foo_##NAME##_buf[sizeof(struct foo) + COUNT * FOO_DATA_SIZE); \
    struct foo *NAME = NULL; \
    __attribute__((__constructor__)) \
    void _foo_##NAME##_init(void) { \
        NAME = foo_init(_foo_##NAME##_buf, sizeof(buf), COUNT); \
    }

DEF_FILE_SCOPE_struct_foo_pnt(foo_at_file_scope, 20)

Does someone has good practices in mind to allocate [flexible array members] on stack?

Don't use them. Instead, use pointers and malloc.

Upvotes: -1

Simon Doppler
Simon Doppler

Reputation: 2093

Variable length arrays (as understood in GNU C) are generally not allocated using alloca. In C90 they are not supported.

The typical way is this:

int main() {
    int n;
    struct foo {
        char a;
        int b[n]; // n needs to be in the same scope as the struct definition
    };

    n = 1;
    struct foo a;
    a.a = 'a';
    a.b[0] = 0;
    // writing a.b[1] = 1 will not cause the compiler to complain

    n = 2;
    struct foo b;
    b.a = 'b';
    b.b[0] = 0;
    b.b[1] = 1;
}

Using -fsanitize=undefined with GCC (more specifically -fsanitize=bounds) will trigger a runtime error on accessing an out-of-bounds VLA member.

Upvotes: 0

Related Questions