【问题标题】:struct中的匿名联合不在c99中?
【发布时间】:2011-03-14 18:35:38
【问题描述】:

这是我遇到的问题的非常简化的代码:

枚举节点类型 { t_int, t_double }; 结构 int_node { 整数值; }; 结构双节点{ 双倍价值; }; 结构节点{ 枚举节点类型类型; 联合{ 结构 int_node int_n; 结构 double_node double_n; }; }; 诠释主要(无效){ 结构 int_node 我; i.value = 10; 结构节点 n; n.type = t_int; n.int_n = i; 返回0; }

我不明白的是:

$ cc us.c $ cc -std=c99 us.c us.c:18:4:警告:声明未声明任何内容 us.c:在函数“main”中: us.c:26:4:错误:“结构节点”没有名为“int_n”的成员

使用没有-std 选项的GCC 编译上面的代码没有任何问题(并且类似的代码运行良好),但似乎c99 不允许这种技术。为什么会这样?是否有可能使c99(或c89c90)兼容?谢谢。

【问题讨论】:

  • 请注意,clang 会默默地编译带有和不带 -std=c99 的给定代码,没有任何错误和警告。

标签: c gcc struct c99 unions


【解决方案1】:

仅用于澄清匿名struct 或匿名union

C11

6.7.2.1 结构和联合说明符

一个未命名成员,其类型说明符是一个结构说明符 无标签称为匿名结构;一个未命名的成员,其类型 说明符是一个联合说明符,带有 no tag 被称为 anonymous 联合。匿名结构或联合的成员被考虑 成为包含结构或联合的成员。这适用 如果包含结构或联合也是匿名的,则递归。

C99 没有匿名结构或联合

简化:类型说明符 标识符 { 声明列表 } 标签 ;

  • 类型说明符structunion
  • 标识符:可选,structunion 的自定义名称;
  • 声明列表:成员、您的变量、匿名struct 和匿名union
  • 标签:可选。如果您在 Type-specifier 前面有 typedef,则 Tags 是别名,而不是 Tags

只有当它没有标识符和标签并且存在于另一个structunion中时,它才是匿名struct或匿名union

struct s {
    struct { int x; };     // Anonymous struct, no identifier and no tag
    struct a { int x; };   // NOT Anonymous struct, has an identifier 'a'
    struct { int x; } b;   // NOT Anonymous struct, has a tag 'b'
    struct c { int x; } C; // NOT Anonymous struct
};

struct s {
    union { int x; };     // Anonymous union, no identifier and no tag
    union a { int x; };   // NOT Anonymous union, has an identifier 'a'
    union { int x; } b;   // NOT Anonymous union, has a tag 'b'
    union c { int x; } C; // NOT Anonymous union
};

typedef hell:如果你有typedef,标签部分不再是标签,它是该类型的别名。

struct a { int x; } A; // 'A' is a tag
union a { int x; } A;  // 'A' is a tag

// But if you use this way
typedef struct b { int x; } B; // 'B' is NOT a tag. It is an alias to struct 'b'
typedef union b { int x; } B;  // 'B' is NOT a tag. It is an alias to union 'b'

// Usage
A.x = 10; // A tag you can use without having to declare a new variable

B.x = 10; // Does not work

B bb; // Because 'B' is an alias, you have to declare a new variable
bb.x = 10;

下面的示例只是将struct 更改为union,工作方式相同。

struct a { int x; }; // Regular complete struct type
typedef struct a aa; // Alias 'aa' for the struct 'a'

struct { int x; } b; // Tag 'b'
typedef struct b bb; // Compile, but unusable.

struct c { int x; } C; // identifier or struct name 'c' and tag 'C'
typedef struct { int x; } d; // Alias 'd'
typedef struct e { int x; } ee; // struct 'e' and alias 'ee'

【讨论】:

  • 你有标签倒退。紧跟在structunion 之后的标识符是标签。对于struct a { int x; } A;,标签是aAstruct 的一个变量。一旦你得到了正确的,那么typedef 就没有特殊情况了,因为astill 标记;只是A变成了一个type而不是一个变量。
【解决方案2】:

查看 C99 的 6.2.7.1,我看到标识符是可选的:

struct-or-union-specifier:
    struct-or-union identifier-opt { struct-declaration-list }
    struct-or-union identifier

struct-or-union:
    struct
    union

struct-declaration-list:
    struct-declaration
    struct-declaration-list struct-declaration

struct-declaration:
    specifier-qualifier-list struct-declarator-list ;

specifier-qualifier-list:
    type-specifier specifier-qualifier-list-opt
    type-qualifier specifier-qualifier-list-opt

我一直在上下搜索,找不到任何关于匿名工会违反规范的参考。整个 -opt 后缀表示事物,在这种情况下 identifier 根据 6.1 是可选的。

【讨论】:

  • 我觉得这里有误会。结构或联合 tag 的标识符是可选的,但不是被声明的标识符。您不能在某个聚合中说 union { ... };,原因与您不能说 int; 的原因相同。在联合情况下,编译器扩展允许这样做,因为您可以在使用匿名联合时在 {...} 部分使用标识符。
【解决方案3】:

另一种解决方案是将公共标头值 (enum node_type type) 放入每个结构中,并使您的顶级结构成为联合。这并不完全是“不要重复自己”,但它确实避免了匿名工会和看起来不舒服的代理值。

enum node_type {
    t_int, t_double
};
struct int_node {
    enum node_type type;
    int value;
};
struct double_node {
    enum node_type type;
    double value;
};
union node {
    enum node_type type;
    struct int_node int_n;
    struct double_node double_n;
};

int main(void) {
    union node n;
    n.type = t_int; // or n.int_n.type = t_int;
    n.int_n.value = 10;
    return 0;
}

【讨论】:

  • 对于像我这样的后期读者:使用模板和适当的 typedef 可以避免 DRY:template<class T> struct base_node{/*[...]*/ T value;}; typedef base_node<int> int_node;
  • 可能在 C++ 中,但不是在 C99 中。
  • 啊,抱歉,在阅读和思考的时候不知怎么忘记了自己是 C 语言……我最终对 C++ 的了解太深了。
  • 我认为在一般情况下,不能保证top union的三个成员会对齐,所以你的解决方案可能行不通。也就是说,我不知道有任何编译器不会将成员设置为预期的对齐方式。
  • @theJPster:这是传统方法,我认为 Common Initial Sequence 保证旨在使其成为可能,但 gcc 和 clang 要么太原始,要么太迟钝(随你选),无法支持它.
【解决方案4】:

匿名联合是 GNU 扩展,不是 C 语言任何标准版本的一部分。您可以将 -std=gnu99 或类似的东西用于 c99+GNU 扩展,但最好编写适当的 C,而不是依赖只提供语法糖的扩展...

编辑:在 C11 中添加了匿名联合,因此它们现在是该语言的标准部分。大概 GCC 的 -std=c11 让你可以使用它们。

【讨论】:

  • 2019 年更新,对于匿名 structunion,无需在当前稳定版本中使用 -std=c11。不确定 GCC 8.2 之前的哪个版本开始采用标准。
  • @UndefinedBehavior GCC 5 已经将__STDC_VERSION__ 扩展为201112L,而 GCC 4.8 根本没有扩展它——两者都使用默认设置。因此,显然,默认为 C11(实际上是 gnu11)的是 GCC 5。
【解决方案5】:

我发现这个问题比其他人晚了大约一年半,所以我可以给出不同的答案:匿名结构不在 C99 标准中,但在 C11 标准中。 GCC 和 clang 已经支持这个(C11 标准似乎已经从微软那里提升了这个功能,并且 GCC 已经为一些 MSFT 扩展提供了支持)。

【讨论】:

  • 具有讽刺意味的是,匿名结构和联合提供的语义在 Dennis Ritchie 1974 年的 C 编译器中可用,我认为 gcc 在 C89 标准之前的日子里已经支持匿名结构和联合。有些人似乎认为这是一个新功能,但它只是代表一种重新获得的能力,不应该失去。
【解决方案6】:

Union 必须有一个名称并像这样声明:

union UPair {
    struct int_node int_n;
    struct double_node double_n;
};

UPair X;
X.int_n.value = 12;

【讨论】:

  • 不在 C11 中,但在 C99 中是的。但是自从它发布以来我们已经过了三年大关,也许是时候开始通过 -std=c11 了:)。
  • 您的代码示例是 C++,而不是 C。union UPair 没有在 C 中声明 UPair 类型。标签和类型的命名空间在 C 中是分开的,但在 C++ 中不是。
【解决方案7】:

嗯,解决方案是命名联合的实例(可以作为数据类型保持匿名),然后将该名称用作代理。

$ diff -u old_us.c us.c --- old_us.c 2010-07-12 13:49:25.000000000 +0200 +++ us.c 2010-07-12 13:49:02.000000000 +0200 @@ -15,7 +15,7 @@ 联合{ 结构 int_node int_n; 结构 double_node double_n; - }; + } 数据; }; 诠释主要(无效){ @@ -23,6 +23,6 @@ i.value = 10; 结构节点 n; n.type = t_int; - n.int_n = i; + n.data.int_n = i; 返回0; }

现在它编译为c99 没有任何问题。

$ cc -std=c99 us.c $

注意:无论如何我对这个解决方案并不满意。

【讨论】:

  • 你应该开心!这是访问联合成员的标准方式,保证可以与自 1970 年 1 月 1 日以来的任何 C 编译器一起使用。
  • 它有点弄脏了代码,不知道为什么它没有包含在 K&R C 中,对我来说似乎是一个简单而有用的功能......无论如何我使用相同的代理方法,但定义宏避免所有的打字。
  • 我意识到这是一篇非常古老的帖子,但是复制实际代码而不是差异补丁更具可读性。
  • @ysap 但是你是怎么发现差异的?
  • @binki 也许我们需要一个 DiffOverflow,人们发布代码的 sn-ps 和答案包含改进它们的差异(可能基于彼此):)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-10-21
  • 2019-06-01
  • 1970-01-01
  • 1970-01-01
  • 2011-07-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多