【问题标题】:How to assign a predefined value to a struct member on type definition?如何在类型定义上为结构成员分配预定义的值?
【发布时间】:2017-04-13 13:45:32
【问题描述】:

这是一个长镜头,但也许会有一些想法。在我编程的系统上,我已经定义了对处理器寄存器进行编程的结构。寄存器由几个字段组成,每个字段有几个位,中间有潜在的“保留”位。写入寄存器时,保留位必须写为零。

例如:

typedef struct {
    uint32_t power    : 3;
    uint32_t reserved : 24;
    uint32_t speed    : 5;
} ctrl_t;

void set_ctrl()
{
    ctrl_t r = {
        .power    = 1;
        .speed    = 22;
        .reserved = 0;
    }

    uint32_t *addr = 0x12345678;

    *addr = *((uint32_t *) &r);

    return;
}

我希望能够将 reserved 字段设置为默认值(在本例中为 0),并且无需显式分配(这在我们的系统中经常发生)。

请注意,如果实例化对象是静态的,那么默认情况下未初始化的字段将为 0。但是,在上面的示例中并不能保证,我还需要设置任意值。

【问题讨论】:

  • 恐怕你不能。如果你的变量不是静态的,那么你需要显式地初始化它。
  • 不保证位域的特定布局。如果您需要,它们不是一个好主意。改用unit32_t 和移位/屏蔽。并且您强制转换调用未定义的行为;您必须将一种类型的对象作为不同类型访问!阅读有效类型(又名严格别名)规则。
  • @Olaf - 在给定的实现(我们的嵌入式开发环境)上,行为应该被很好地定义(因为它恰好是)。我使用 all-uint32_t 结构的事实确保字段是右对齐的,并且该结构的大小正好是 32 位。
  • @Olaf - 你是对的,虽然 如果我混合成员类型,不能保证对齐。最初,我根据枚举定义字段,虽然枚举被提升为整数,但编译器没有正确打包它们,即使我添加了packed 修饰符。
  • 只需省略 .reserved = 0; 。未提及的字段设置为 0。

标签: c struct types initialization c99


【解决方案1】:

结构 type C 中的定义不能表达结构成员的值。没有它的机制。结构instance定义都可以。

我希望能够将保留字段设置为默认值(0 在 这个例子),并避免需要显式分配(其中 在我们的系统中经常发生)。

注意,如果实例化的对象是静态的,那么默认情况下 未初始化的字段将为 0。但是,在上面的示例中,有 不保证,而且我需要设置任意值。

你想要的默认值是 0 是偶然的。不过,您似乎有一个误解:您不能部分初始化 C 对象。如果在结构对象的声明中提供初始化程序,则任何未显式初始化的成员都会获得与对象具有静态存储持续时间且没有初始化程序时相同的值。

因此,您可以这样做:

void set_ctrl() {
    ctrl_t r = {
        .power    = 1,
        .speed    = 22,
        // not needed:
        // .reserved = 0
    };

    // ...

如果您想要一种简单的方法来使用一组默认值(一些非零)初始化整个结构,那么您可以考虑为初始化器编写一个宏:

#define CTRL_INITIALIZER { .power = 1, .speed = 22 }

// ...

void set_other_ctrl() {
    ctrl_t r = CTRL_INITIALIZER;
    // ...

同样,您可以为初始化程序的部分内容定义宏:

#define CTRL_DEFAULTS .power = 1 /* no .speed = 22 */

// ...

void set_other_ctrl() {
    ctrl_t r = { CTRL_DEFAULTS, .speed = 22 };
    // ...

在这种情况下,您甚至可以覆盖默认值:

    ctrl_t r = { CTRL_DEFAULTS, .power = 2, .speed = 22 };

...但重要的是要记住只使用指定的成员初始化器,如上所述,而不是未指定的值。

【讨论】:

  • 谢谢。您答案的第二部分(部分初始化对象的未初始化成员设置为 0)对我来说确实是新的。但是,这只是部分解决了我的问题(在示例中,仅适用于保留位)。正如我所提到的,我想设置一个任意值。
  • 我没有关注,@ysap。你的意思是你想要整个结构的默认初始化器,将一些成员初始化为非零值?
  • 我想要 一些 成员的默认初始化程序,但我不关心其他人的默认初始化程序,因为这可以被显式运行时初始化覆盖。跨度>
  • 很好,@ysap,我认为我更新后的答案涵盖了您想要的,或者至少是 C 可以做到的。
  • @ysap 覆盖默认值可能会引发类似“警告:已覆盖初始化字段 [-Woverride-init]”的警告。根据需要使用编译器选项控制来消除该警告。
【解决方案2】:

做不到。

值在 C 中没有 C++ 意义上的“构造函数”。无法保证在创建特定类型的值时运行任意代码,因此无法做到这一点。事实上,值的“创造”在 C 语言中是一个相当失败的概念。

考虑一下:

char buf[sizeof (ctrl_t)];
ctrl_t * const my_ctrl = (ctrl_t *) buf;

在此代码中,指针分配还必须包含将buf 的位设置为各种默认值的代码,以使其按您想要的方式工作。

在 C 中,“所见即所得”通常成立,并且生成的代码通常非常可预测,或者由于优化而更好。但这种“神奇”的副作用实际上并不是 C 语言的工作方式。

最好不要暴露“原始”寄存器,而是抽象出保留位的存在:

void set_ctrl(uint8_t power, uint8_t speed)
{
  const uint32_t reg = ((uint32_t) power << 29) | speed;
  *(uint32_t *) 0x12345678 = reg;
}

这会显式计算reg,将未使用的位设置为 0。您当然可以添加断言以确保不超过 3 位和 5 位范围限制。

【讨论】:

  • 谢谢。是的,我认为这可能是不可能的,这就是为什么我在我的问题中说“远射”。我练习,我们确实使用宏来访问 regs,将字段屏蔽为正确的大小。当您有 许多 个寄存器时,您对接口函数的建议是不切实际的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-05-08
  • 1970-01-01
相关资源
最近更新 更多