使用指向复合文字的指针初始化块作用域静态常量变量?

问题描述 投票:0回答:2

以下代码被 GCC 和 Clang 拒绝(godbolt 链接):

struct thing;

typedef enum {
  THING_TYPE_A,
  THING_TYPE_B,
} thing_type_t;

typedef struct thing_a {
  int i;
} thing_a_t;

typedef struct thing_b {
  struct thing const *t;
} thing_b_t;

typedef struct thing {
  thing_type_t type;
  union {
    thing_a_t a;
    thing_b_t b;
  } t;
} thing_t;

thing_t const *get_thing(void) {
  static const thing_t s_thing = {
    .type = THING_TYPE_B, 
    .t = {
      .b = { 
        .t = &(thing_t) { .type = THING_TYPE_A, .t = { .a = { .i = 234 } } } 
      }
    },   
  };

  return &s_thing;
}

cppreference 在复合文字上的页面 说:

如果复合文字出现在文件范围内,则复合文字评估的未命名对象具有静态存储持续时间,如果复合文字出现在块范围内,则具有自动存储持续时间(在这种情况下,对象的生命周期在封闭块的末尾结束) .

我相信这解释了编译错误;用于初始化

thing_t
地址的匿名
s_thing.t.b.t
具有自动存储持续时间,因此不是编译时常量。如果
s_thing
被移动到文件范围,Clang 和 GCC 都会接受它。 (在 this SO question 有更多讨论)

看起来 C23 将通过允许在复合文字括号内指定

constexpr
来扩展它,这是一个受欢迎的改进!

与此同时,是否有任何方法可以在 C23 之前的块范围内实现像

s_thing
这样的声明(即包含指向另一个常量变量的指针的静态 const 结构的初始化),而不必显式声明匿名
thing_t
作为自己的独立变量?

c language-lawyer c99 compound-literals c23
2个回答
1
投票

你对错误的原因是正确的:块范围内的复合文字总是有自动存储持续时间,即使你试图使用它们来初始化

static
对象。

如果主要动机是不允许

s_thing
以该名称作为全局对象可见,您可以将其定义为源文件中的
static
文件范围变量本身以及
get_thing
以返回其地址.

thing.h:

struct thing;

typedef enum {
  THING_TYPE_A,
  THING_TYPE_B,
} thing_type_t;

typedef struct thing_a {
  int i;
} thing_a_t;

typedef struct thing_b {
  struct thing const *t;
} thing_b_t;

typedef struct thing {
  thing_type_t type;
  union {
    thing_a_t a;
    thing_b_t b;
  } t;
} thing_t;

thing_t const *get_thing(void);

thing.c:

static const thing_t s_thing = {
    .type = THING_TYPE_B, 
    .t = {
      .b = { 
        .t = &(thing_t) { .type = THING_TYPE_A, .t = { .a = { .i = 234 } } } 
      }
    },   
};

thing_t const *get_thing(void)
{
  return &s_thing;
}

如果您真的想在文件范围内使用它,正如您所说,您唯一的选择是使用单独的命名静态对象而不是复合文字:

thing_t const *get_thing(void) {
  static const thing_t tmp_thing = 
          { .type = THING_TYPE_A, .t = { .a = { .i = 234 } } };
  static const thing_t s_thing = {
    .type = THING_TYPE_B,
    .t = {
      .b = {
        .t = &tmp_thing
      }
    },
  };

  return &s_thing;
}

1
投票

与此同时,是否有任何方法可以在 C23 之前的块范围内实现像

s_thing
这样的声明(即包含指向另一个常量变量的指针的静态 const 结构的初始化),而不必显式声明匿名
thing_t
作为自己的独立变量?

不,你已经有效地排除了所有的可能性。

  • 具有静态存储持续时间的对象的初始化程序可能仅包含常量表达式。

  • 对于指针类型的对象或子对象,如果有对应的初始化元素,则必须具体为地址常量,该地址常量可以是空指针常量,也可以是转换为指针类型的整型常量表达式,也可以是指向具有静态存储持续时间的对象,或指向函数的指针。

  • 唯一具有静态存储持续时间但没有关联标识符的对象是字符串文字对应的数组和出现在文件范围内的复合文字。

而且我认为 C23 中没有什么不同。是的,您可以在复合文字的声明中使用

constexpr
来获得结构类型的“复合文字常量”,但据我所知,这不会赋予所述对象静态存储持续时间。如果对象没有静态存储持续时间,那么它的地址就不是地址常量。

© www.soinside.com 2019 - 2024. All rights reserved.