C99中是否不支持结构体中的匿名联合?

53
这是我遇到的问题的非常简化的代码:
枚举节点类型{t_int,t_double};
结构体“int_node”{ int value; };
结构体“double_node”{ double value; };
结构体“node”{ enum node_type type; 联合{ struct int_node int_n; struct double_node double_n; }; };
主函数{ struct int_node i; i.value = 10; struct node n; n.type = t_int; n.int_n = i; return 0; }
以下是我不理解的部分: $ cc us.c $ cc -std=c99 us.c us.c:18:4: warning: declaration does not declare anything us.c: In function ‘main’: us.c:26:4: error: ‘struct node’ has no member named ‘int_n’
在没有任何问题的情况下编译上面的代码(类似的代码运行良好),但是似乎c99不允许这种技术。为什么会这样?是否可能使其与c99(c89、c90)兼容?谢谢。

3
请注意,使用clang编译此代码时,无论是否使用“-std=c99”参数,都可以默默地编译通过,没有任何错误或警告。 - Martin
7个回答

65

匿名联合是GNU扩展,不是C语言任何标准版本的一部分。您可以使用-std = gnu99或类似选项进行c99 + GNU扩展,但最好编写正确的C代码,而不是依赖提供纯粹语法糖的扩展...

编辑:匿名联合已添加到C11中,因此它们现在是该语言的标准组成部分。据推测,GCC的-std=c11可以让您使用它们。


1
更新于2019年,当前稳定版本的匿名structunion不需要使用-std=c11。不确定GCC 8.2之前的版本采用了什么标准。 - Undefined Behavior
1
@UndefinedBehavior GCC 5已将__STDC_VERSION__扩展为201112L,而GCC 4.8则根本不扩展它 - 这两者都是使用默认设置。因此,显然,GCC 5默认为C11(实际上是gnu11)。 - Ruslan

30

我发现这个问题比其他人晚了一年半,因此我可以给出一个不同的答案:匿名结构体不在C99标准中,但它们在C11标准中。GCC和clang已经支持此功能(C11标准似乎从微软那里借鉴了此功能,而GCC已经为某些MSFT扩展提供了支持一段时间)。


18
具有匿名结构体和联合体提供的语义在Dennis Ritchie于1974年编写的C编译器中就已经存在,我认为gcc在C89标准出现之前就支持匿名结构体和联合体了。一些人似乎认为这是一个新功能,但它实际上只是一个被重新获得的本不应该丢失的能力。 - supercat

5
解决方法是给联合体的实例(可以作为数据类型匿名)命名,然后使用该名称作为代理。现在它可以在没有任何问题的情况下编译为 c99 。

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

3
你应该感到高兴!这是访问联合成员的标准方式,自1970年1月1日以来保证能与任何C编译器一起使用。 - Jens
2
它会让代码变得有些丑陋,我不知道为什么它没有被包含在K&R C中,对我来说似乎是一个简单而有用的功能...无论如何,我使用相同的代理方法,但定义宏以避免所有的打字。 - Arran Cudbard-Bell
4
我知道这是一个非常古老的帖子,但直接复制实际代码比使用差异补丁更易读。 - ysap
1
@ysap 但是你是如何发现区别的呢? - binki
2
@binki 或许我们需要一个 DiffOverflow,人们可以在其中发布代码片段,答案由改进它们的差异组成(可能基于彼此):) - Thomas
1
@Thomas,它被称为Github ;) - Gauthier

5

关于匿名struct或匿名union的澄清。

C11

6.7.2.1 结构体和联合体规范

类型说明符为结构体且没有标记的未命名成员被称为匿名结构体; 类型说明符为联合体且没有标记的未命名成员被称为匿名联合体。匿名结构体或联合体的成员被视为包含结构体或联合体的成员。如果包含结构体或联合体也是匿名的,则递归应用此规则。

C99 没有匿名结构体或联合体

简化后的代码如下:类型说明符 标识符 { 声明列表 } 标签 ;
  • 类型说明符structunion
  • 标识符:可选,用于自定义 structunion 的名称;
  • 声明列表:成员变量、匿名 struct 和匿名 union
  • 标签:可选。如果在 类型说明符 前有一个 typedef,则 标签 是别名而不是标签。
仅当匿名 struct 或匿名 union 没有标识符和标签,并且存在于另一个 structunion 中时,它才是匿名的。
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的作用是创建类型别名,如果你使用了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'


3
你的标签是反过来的。在 structunion 之后的标识符才是标签。对于 struct a { int x; } A;,它的标签是 a,而 A 是该结构体的变量。一旦你弄清楚了这个问题,那么 typedef 就没有特殊情况了,因为 a 仍然是标签;只是 A 变成了类型而不是变量。 - Paul J. Lucas

1

Union必须有一个名称,并且声明方式如下:

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

UPair X;
X.int_n.value = 12;

3
不在C11中,但在C99中是可以的。但由于它已经发布了三年,也许现在是时候开始使用-std=c11了。 - Arran Cudbard-Bell
2
你的代码示例是C++,而不是C。在C中,union UPair并没有声明UPair类型。在C中,标签和类型的命名空间是分开的,但在C++中不是。 - Patrick Schlüter

1

看到 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规定。


4
我认为这里存在着误解。 结构体或联合体的标识符(tag)是可选的,但正在声明的标识符不是可选的。你不能在某个聚合体中写 union { ... }; 的语法,就像你不能写 int; 一样。在联合体的情况下,编译器会通过扩展来允许使用匿名联合体的 {...} 部分中的标识符。 - Jens

1
另一种解决方案是将通用头部值(enum node_type type)放入每个结构中,并使您的顶层结构成为联合体。这并不完全符合"Don't Repeat Yourself"原则,但它避免了匿名联合和难看的代理值。
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;
}

对于像我这样的后来者:DRY可以通过使用模板和适当的typedef避免:template<class T> struct base_node{/*[...]*/ T value;}; typedef base_node<int> int_node; - Aconcagua
3
可能在C++中可以实现,但在C99中不行。 - theJPster
啊,抱歉,不知怎么的,在阅读和思考时忘记了自己在使用C语言...我太深入C++了。 - Aconcagua
我认为在一般情况下,不能保证顶部联合的三个成员对齐,因此您的解决方案可能无法正常工作。话虽如此,我不知道有任何编译器不会按照预期的对齐方式设置成员。 - ysap
@theJPster:那是传统的方法,我认为共同初始序列保证旨在使其成为可能,但gcc和clang要么太原始,要么太愚钝(你可以选择)不支持它。 - supercat

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接