假设我们有一个以灵活数组成员结尾的结构:
struct foo {
size_t len;
uint8_t data[];
};
如何在堆栈上分配这个结构(即内存在作用域结束时自动释放)?此外,如果len
可以包含字段data
的大小,那就太好了。
目前,我做的事情有:
uint8_t buf[256];
struct foo *foo = (struct foo *)buf;
foo->len = sizeof(buf) - sizeof(struct foo);
然而,它很容易出错。alloca()
的使用可能稍微好一点:
struct foo *foo = alloca(256 + sizeof(struct foo));
foo->len = 256;
从那里,我可以定义这样一个宏:
#define STACK_ALLOC_FOO(SIZE) ({
struct foo *_tmp = alloca(SIZE + sizeof(struct foo));
_tmp->len = SIZE;
_tmp;
})
并声明:
struct foo *foo = STACK_ALLOC_FOO(256);
然而,我不确定用alloca()
分配的内存的使用寿命。是内部范围还是功能?
此外,分配全局变量是不起作用的(即使这不是我主要关心的问题)。
是否有人考虑过在堆栈上分配具有灵活数组成员的结构的良好做法?
假设我们有一个以可变长度数组(VLA)结尾的结构:
嗯,你没有。您有一个以灵活数组成员结尾的结构。不同的东西,主要用于动态内存分配场景。
如何在堆栈上分配此结构
如果没有一些非标准的扩展,很难做到这一点。例如,alloca
扩展保证返回没有有效类型的内存。这意味着编译器尚未将内存内部标记为具有特定类型。否则
structfoo*foo=(structfoo*)buf;
您会得到严格的别名冲突未定义行为,就像上面的错误代码一样。什么是严格的混叠规则?
此外,您还需要注意对齐&衬料
然而,我不确定用alloca()分配的内存的生存期。是内部范围还是功能?
可能吧。它不是一个标准函数,我不确定任何lib是否能为其行为提供可移植的保证。它甚至不是POSIX函数。Linuxman
保证:
alloca()函数在调用方的堆栈帧中分配大小字节的空间。当调用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;
这里CCD_ 7和CCD_。您可以通过foobar256
的f.data
或b.data
访问数据。这种类型的双关语在C.中是允许的,并且定义明确
在这一点上,你可能会意识到这个结构比它更麻烦,只需要使用两个局部变量,其中一个是实际的VLA:
size_t len = ... ;
uint8_t data[len];
作为替代方案,我建议:
#define DECLARE_FOO(NAME, SIZE)
struct {
struct foo __foo;
char __data[SIZE];
} __ ## NAME = {
.__foo.len = SIZE,
};
struct foo *NAME = &(__ ## NAME).__foo;
所以你可以做:
DECLARE_FOO(var, 100);
它不是很优雅。但是,它可以声明全局/静态变量,并且不依赖于任何强制转换运算符。
可变长度数组(如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;
}
将-fsanitize=undefined
与GCC(更具体地说是-fsanitize=bounds
)一起使用将在访问越界VLA成员时触发运行时错误。
如果您打算这样使用它:
#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]%sn", h->len, h->data);
}
int main(int argc, char *argv[])
{
print_foo();
return 0;
}
正因为如此:
alloca()分配的空间不会自动释放如果指向它的指针超出了范围。
它将生成完全有效的代码,因为唯一超出作用域的是*_tmp
,并且它不会解除分配,您仍然在同一堆栈帧内。它确实会随着print_foo的返回而被取消分配。
实际上,了解编译器如何处理优化标志和程序集输出是非常有趣的。(如果使用-O3,则与分配相关的代码在main
中完全重复)
希望这能帮助
如何在堆栈上分配具有可变长度数组(VLA)的结构
您必须确保缓冲区正确对齐。使用CCD_ 17或仅CCD_;字节";,CCD_ 19表示8位数字。
#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);
我可以定义这样的宏:
({
是gcc扩展。你也可以定义一个宏来定义变量,比如:
// 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);
}
使用alloca()可能会稍微好一点:
除非您碰巧在循环中调用alloca()
。。。
我不确定用alloca()分配的内存的生存期。是内部范围还是功能?
用alloca分配的内存在函数结束时还是在作用域结束时被释放?
分配全局变量是不起作用的(即使它不是我主要关心的问题)。
这将很难-C没有构造函数。您可以使用外部工具或实验预处理器魔术来生成代码,如:
_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;
即,您必须初始化缓冲区,而不是结构。我想我会用C语言写一个工具,使用相同的编译器和相同的选项来生成代码(对于gcc-ish环境中的交叉编译,我只会编译一些初始化为ELF文件的可执行文件,而不是运行它,而是用objdump
从ELF文件中获取初始化,并处理它来生成C源代码)。
或者,您可以(ab-)使用GCC扩展__attrbute__((__constructor__))
-在另一个宏中定义具有该属性的函数。大致情况:
#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)
是否有人在堆栈上分配[灵活数组成员]的良好实践?
不要使用它们。相反,使用指针和malloc。