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

5

8

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?

Valdes answered 29/6, 2021 at 9:18 Comment(8)
memory allocated with alloca is killed when the function returns. which means you have no chance of returning fooRobbierobbin
@Robbierobbin The question is about a variable on the stack. This already doesn't show any intention to return the address of the variable from the function. Please read: "(ie. memory is automatically released at the end of the scope)"Willodeanwilloughby
Does someone has good practices in mind to allocate VLA on stack? Don't use them. That is not a struct with VLA but a struct with flexible array member. Variable lenght array is not a flexible array member, those are different terms.Vicentevicepresident
From the alloca man page: "This temporary space is automatically freed when the function that called alloca() returns to its caller."Foppish
Have you produced the assembly for it?Seriema
@AhmedMasud I have done a few tests and it works. But I think it is not sufficient to guarantee it is correct.Taut
Global variables are allocated before the program start up, so you cannot usefully have a variable-length anything global.Defroster
What you do currently is UB for more than one reason. alloca is much better. Not portable, but at least an implementation that provides alloca presumably knows what it's doing. Lifetime of memory allocated with alloca is the same as for automatic variables of the function so nothing is lost or gained.Defroster
F
7

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];
Folksy answered 29/6, 2021 at 10:14 Comment(4)
That alloca returns memory without an effective type is no different than the fact that malloc returns memory without an effective type. Certainly it is intended for memory returned by malloc to be usable for structures with flexible array members, so it would work with alloca too.Lepley
@EricPostpischil Since it is not a standard function, there's no guarantees. It could be inlined or a function-like macro resulting into some character array of bytes, for example. Which might then get compiled just as regular application code.Folksy
The rule in 6.5 6 about effective types is not specific to malloc; it applies to all memory without a declared type: “If a value is stored into an object having no declared type through an lvalue having a type that is not a character type, then the type of the lvalue becomes the effective type of the object…” So, if alloca is merely an extension that provides memory, then this rule applies to it. There would be no problem unless alloca is specifically documented to override 6.5 6.Lepley
@Folksy Since there is no standard function,. there are no guarantees in the standard. Whoever supplies the function must also supply a suitable guarantee to go with it. It's in the consumer protection statute.Defroster
V
1

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.

Valdes answered 19/7, 2021 at 8:25 Comment(3)
Interesting. There might be some padding issues but it is unlikely. You don't need a cast when setting name. &(__ ## NAME).__foo) would sufficeHeadley
Indeed. I have applied your suggestion.Taut
I have found the fifo implementation of the Linux kernel more or less use this idea: elixir.bootlin.com/linux/latest/source/include/linux/kfifo.hTaut
G
0

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.

Gomel answered 29/6, 2021 at 9:41 Comment(4)
You can't put a VLA in a struct.Foppish
What would be the declaration of a function that takes a struct foo as parameter?Taut
@Foppish you are correct, what I wrote is only true for GNU C, I edited the answer.Gomel
@JérômePouiller this will get insanely complex, as you cannot modify the VLA length at file scope, but this also means it is not visible outside of the function scope at which it is defined. I think you are better off looking for flexible array member, it will probably better fit your needs.Gomel
S
0

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

Seriema answered 29/6, 2021 at 10:4 Comment(0)
V
-1

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.

Vicentevicepresident answered 29/6, 2021 at 9:55 Comment(1)
struct foo *foo = (struct foo *)buf; results in an aliasing violation when foo is used to access the desired structure, even if buf is correctly aligned. (Also, the conversion of the char * from buf to struct foo * is not fully defined by the C standard, although that is less of a practical consideration.)Lepley

© 2022 - 2024 — McMap. All rights reserved.