我有一个包含几个const数组字段的大对象,它们看起来像这样:
struct test {
const int vals[99999999];
};
我想使用指定的初始化程序来创建结构,因为真正的结构有很多字段。
结果,我尝试了这个
#include <stdlib.h>
struct test {
const int vals[99999999];
};
int main()
{
struct test first = {.vals[4]=4};
return 0;
}
不出所料,这在运行时失败,因为结构太大而无法放在堆栈上。
然后我尝试了
#include <stdlib.h>
struct test {
const int vals[99999999];
};
int main()
{
struct test * t = malloc(sizeof(struct test));
*t = (struct test){.vals[4]=4 };
return 0;
}
当我用以下代码编译它时,它会失败:
test.c:9:8 error: assignment of read-only location '*t'
是否可以使用指定的初始化程序来创建此结构?
答案 0 :(得分:2)
不,当然不是没有丢掉const
。
您不能说“此成员无法分配”,然后继续并分配给它而不会收到警告。
我让这个工作:
struct test *t = malloc(sizeof *t);
memcpy(t, &(struct test) { .vals[4] = 4 }, sizeof *t);
但我真的不认为它更好;它可能花费很多,因为被复制的值必须存在于某处(我们正在复制*t
的完整大小,其中包括所有大型数组)。
也许最好切换到拥有全局预初始化版本,您可以根据需要访问该版本:
static const struct test test_template = { .vals[4] = 4; };
然后你可以这样做:
struct test foo = test_template;
这是有效的,因为它是初始化而不是赋值。通过使其成为全局,它将大的“模板对象”推入全局数据,即从堆栈中移出。
对于堆分配的实例,您可以执行以下操作:
struct test * const foo = malloc(sizeof *foo);
memcpy(foo, &test_template, sizeof test_template);
此将覆盖const
中的vals
数据,这可能不是很漂亮,但应该是安全的。我想。
我之前尝试使用初始化函数,但由于它已分配给vals
,因此错误。遗憾!
答案 1 :(得分:0)
在任何情况下,struct test
都会炸毁堆栈,因此您需要将其减少到可管理的范围。
试试这个:
struct Initialiser
{
size_t index;
int val;
}
void init_test(struct test * pt, struct Initialiser * initialiser, size_t size)
{
while (size > 0)
{
--size;
memcpy((char*)pt + initialiser[size].index * sizeof *(pt->vals) ,
&initialiser[size].val,
sizeof pt->vals[initialiser[size].index]);
}
}
int main(void)
{
struct Initialiser initialiser[] =
{
{1, 42},
{4, 4},
...
}
struct test * t = malloc(sizeof *t);
init_test(t, initialiser, sizeof initialiser / sizeof *initialiser);
...
如果上面定义的struct Initialiser
也变得很大,则使用几个,每个都在一个单独的上下文/范围中定义。
答案 2 :(得分:0)
将初始化器与动态内存分配混合起来可能没什么意义。大多数情况下,初始化程序指的是启动条件,而动态分配总是在运行时发生。
我无法提出任何你无法做到这一点的理由:
struct test * t = malloc(sizeof(struct test));
t->vals[4] = 4;
这比使用memcpy和临时堆栈对象的任何解决方案都要快得多并且消耗的内存更少。创建一个大小为99999999整数的临时复合文字是疯狂的。你不应该仅为了使用指定的初始化程序而编写明显低效的程序。
如果您需要清空未使用的项目,请使用calloc
。
答案 3 :(得分:0)
在C的大多数实现中,结构中数组的偏移量将与其大小无关。虽然标准不要求,并且因此不能要求结构中两个数组的公共部分表现为公共初始序列,但数十年来编译器支持如下构造:
struct thing2 { int size; int data[2]; };
struct thing3 { int size; int data[3]; };
struct thing4 { int size; int data[4]; };
struct thing_arbitrary { int size; int data[99999]; };
void print_thing_contents(void *p)
{
int i;
struct thing_arbitrary *it = (thing_arbitrary*)p;
for (int i=0; i<it->size; i++)
printf("%d ", it->dat[i]);
}
print_thing_contents()
能够接受任何结构
如果size
成员设置得恰当,则表示种类。
使用该模式,可以使用结构创建初始化常量 适合上述模式,例如
struct { int size; int data[5]; } my_fiver = {5,{1,2,3,4,5}};
并将其与print_thing_constants
一起使用。对于C99,在thing_arbitrary
中使用灵活的数组成员是有意义的,并且别名规则需要以下内容:
struct my_fiver_type { int size; int data[5]; } my_fiver =
{5,{1,2,3,4,5}};
union my_fiver_dummy_union
{ struct my_fiver_type v1; struct thing_arbitrary v2; }
让编译器知道struct my_fiver_type
类型的东西可能使用struct thing_arbitrary
类型的指针访问它们的公共初始序列。遗憾的是,标准仅保证size
将被视为CIS的成员,并且无法将其扩展到dat
的前五项。此外,虽然上面应该注意到质量编译器需要识别两种类型之间的别名,但gcc支持这种代码的唯一方法是通过-fno-strict-aliasing
完全禁用别名分析(这可能是个好主意)无论如何,直到gcc开始表彰标准)。