如何在堆栈上分配具有灵活数组成员的结构

Jér*_*ler 8 c flexible-array-member

假设我们有一个以灵活数组成员结尾的结构:

struct foo {
    size_t len;
    uint8_t data[];
};
Run Code Online (Sandbox Code Playgroud)

如何在堆栈上分配这个结构(即内存在作用域结束时自动释放)?另外,如果len能包含字段的大小就更好了data

目前,我做的事情如下:

uint8_t buf[256];
struct foo *foo = (struct foo *)buf;
foo->len = sizeof(buf) - sizeof(struct foo);
Run Code Online (Sandbox Code Playgroud)

然而,它很容易出错。使用alloca()可能会稍微好一点:

struct foo *foo = alloca(256 + sizeof(struct foo));
foo->len = 256;
Run Code Online (Sandbox Code Playgroud)

从那里,我可以定义一个像这样的宏:

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

并声明它:

struct foo *foo = STACK_ALLOC_FOO(256);
Run Code Online (Sandbox Code Playgroud)

但是,我不确定分配的内存的生命周期alloca()。是内部作用域还是函数?

另外,分配全局变量不起作用(即使这不是我主要关心的问题)。

有人有在堆栈上分配具有灵活数组成员的结构的良好实践吗?

Lun*_*din 7

假设我们有一个以可变长度数组 (VLA) 结尾的结构:

好吧,你不知道。您有一个以灵活数组成员结尾的结构。不同的东西,主要用于动态内存分配场景。

如何在堆栈上分配该结构

如果没有一些非标准扩展,很难做到这一点。例如,alloca保证返回没有有效类型的内存的扩展。这意味着编译器尚未在内部将内存标记为具有某种类型。否则...

结构 foo *foo = (结构 foo *)buf;

您会得到严格的别名违规未定义行为,就像上面的错误代码一样。严格的别名规则是什么?

此外,您还需要注意对齐和填充。

但是,我不确定用 alloca() 分配的内存的生命周期。是内部作用域还是函数?

是的,可能是。它不是一个标准函数,我不确定是否有任何库对其行为提供了可移植的保证。它甚至不是 POSIX 函数。Linuxman保证:

alloca() 函数在调用者的堆栈帧中分配 size 字节的空间。当调用 alloca() 的函数返回到其调用者时,该临时空间会自动释放。

我假设这适用于 *nix 下的 gcc/glibc,但不适用于其他工具链或系统。


为了获得可移植且坚固的代码,您可以做的是这样的:

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;
Run Code Online (Sandbox Code Playgroud)

这里bar256foobar256可以在本地定义。您可以通过 或f.data访问b.data数据foobar256。这种类型双关在 C 中是允许的并且是明确定义的。

此时,您可能会意识到该结构只是更麻烦,值得,只需使用两个局部变量,其中一个是实际的 VLA:

size_t len = ... ;
uint8_t data[len];
Run Code Online (Sandbox Code Playgroud)

  • “alloca”返回没有有效类型的内存与“malloc”返回没有有效类型的内存没有什么不同。当然,它的目的是让“malloc”返回的内存可用于具有灵活数组成员的结构,因此它也可以与“alloca”一起使用。 (3认同)
  • 6.5 6 中关于有效类型的规则并不特定于 `malloc`;它适用于所有没有声明类型的内存:“如果通过类型不是字符类型的左值将值存储到没有声明类型的对象中,那么左值的类型将成为该对象的有效类型...... ” 所以,如果`alloca`只是一个提供内存的扩展,那么这条规则就适用于它。除非专门记录了“alloca”来覆盖 6.5 6,否则不会有问题。 (2认同)

Sim*_*ler 0

可变长度数组(如GNU C中所理解的)通常不使用 进行分配alloca。在 C90 中,它们不受支持。

典型的方式是这样的:

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;
}
Run Code Online (Sandbox Code Playgroud)

-fsanitize=undefined与 GCC(更具体地说)一起使用-fsanitize=bounds将在访问越界 VLA 成员时触发运行时错误。