【问题标题】:Designated Initializers with heap objects带有堆对象的指定初始化器
【发布时间】:2016-11-01 11:22:46
【问题描述】:

我有一个大对象,其中有几个字段是 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'

是否可以使用指定的初始化器来创建这个结构?

【问题讨论】:

  • 如果你打算稍后赋值,为什么数组需要const
  • @usr 我只在初始化时赋值

标签: c struct


【解决方案1】:

不,当然不会抛弃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);

覆盖vals 中的const 数据,这可能不是很漂亮,但应该是安全的。我想。

我之前尝试过使用初始化函数,但由于它分配给vals,所以这是错误的。对不起!

【讨论】:

  • t-&gt;vals[4] = 4; 不会像 OP 报告的那样给出“相同”的错误,即分配给只读位置吗?
  • 复合文字也将在堆栈上创建,因此也会炸毁后者。
  • 我认为 memcpy 的第一个想法是我需要的。在这种情况下,性能并不是真正的问题。
  • 请注意,您的两个示例并不相同。第一个代码将所有未使用的项目初始化为零,第二个代码将它们作为垃圾。
  • @Lundin 好点,我没在意。已修复,当然感谢。
【解决方案2】:

一开始就将初始化程序与动态内存分配混合起来可能没有多大意义。大多数情况下,初始化器指的是启动条件,而动态分配总是发生在运行时。

我想不出任何你不能这样做的理由:

struct test * t = malloc(sizeof(struct test));
t->vals[4] = 4;

这比任何围绕 memcpy 和临时堆栈对象的解决方案都要快得多,并且消耗的内存要少得多。创建一个大小为 99999999 个整数的临时复合文字是疯狂的。您不应该仅仅为了使用指定的初始化程序而编写公然低效的程序。

如果您需要将未使用的项目归零,请使用calloc

【讨论】:

  • 这个解决方案没有@alk在unwind的原始答案中指出的相同问题吗?
  • @DavidBowling 当然,您不能在运行时写入只读内存位置。如果 OP 需要这样做,他们显然必须删除 const 关键字。
  • 啊。这就是我所缺少的。感谢您为我解决这个问题。
  • @DavidBowling 保留只读属性的一种可能解决方案是分配和初始化结构,然后在完成后,仅通过 const struct test* 取消引用它。
【解决方案3】:

在任何情况下,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 也变得很大,请使用多个,每个都在单独的上下文/范围中定义。

【讨论】:

    【解决方案4】:

    在 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 开始遵守标准)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-05-27
      • 1970-01-01
      • 2013-06-27
      • 1970-01-01
      • 2018-02-03
      • 2023-04-09
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多