带有堆对象的指定初始化程序

时间:2016-11-01 11:22:46

标签: c struct

我有一个包含几个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'

是否可以使用指定的初始化程序来创建此结构?

4 个答案:

答案 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开始表彰标准)。