在C11中,匿名结构体和联合体何时有用?

67

C11增加了“匿名结构体和联合体”等新特性。

我搜索了一下,但没有找到一个清晰的解释来说明何时使用匿名结构体和联合体会有用。 我问这个问题是因为我不完全理解它们是什么。 我知道它们是没有名称的结构体或联合体,但我通常将其视为错误,因此我只能想象命名结构体的用途。


可能是如何在C语言中使用匿名结构体/联合体?的重复问题。 - wallyk
2
@wallyk 不是完全相同的问题。 - Luchian Grigore
6个回答

75

在实践中,结构体内部的匿名联合非常有用。考虑您想要实现一个区分的总和类型(或标记联合),它是一个聚合体,具有一个布尔值和一个浮点数或者一个char*(即字符串),取决于布尔标志。使用C11,您应该能够编写:

typedef struct {
    bool is_float;
    union {
       float f;
       char* s;
    };
} mychoice_t;

double as_float(mychoice_t* ch) 
{ 
   if (ch->is_float) return ch->f;
   else return atof(ch->s);
}

使用C99,你需要给联合命名,并编写 ch->u.fch->u.s 这种不太易读且更冗长的代码。

另一种实现一些带标记的联合类型的方法是使用强制类型转换。 Ocaml运行时提供了许多示例。

Common Lisp的SBCL实现确实使用一些union来实现带标记的联合类型。而GNU make也使用它们。


2
啊,所以等一下,手头的问题是C11增加了对“结构体/联合体内部的匿名结构体和联合体”的支持? - griotspeak
4
我能想到最有用的情况至少是这样。事实上,GCC很久以前就支持了这个扩展,我一直为此感到庆幸... - Basile Starynkevitch
谢谢。那很有道理,我现在至少明白了一个应用程序。 - griotspeak
请注意,在C语言中,bool不是默认类型,bool仅在C++中有效。 - Renato
20
在C99中,<stdbool.h>提供了bool类型。 - cjh

64

匿名结构体和联合体的典型实际用途是提供数据的另一种视角。例如,在实现3D点类型时:

typedef struct {
    union{
        struct{
            double x; 
            double y;
            double z;
        };
        double raw[3];
    };
}vec3d_t;

vec3d_t v;
v.x = 4.0;
v.raw[1] = 3.0; // Equivalent to v.y = 3.0
v.z = 2.0;

如果你需要将一个3D向量作为指向三个双精度浮点数的指针传递给代码,那么这很有用。与其使用丑陋的f(&v.x),不如使用f(v.raw)来表达你的意图。


3
可能是成员类型,但不是通过获取所访问的成员的地址来形成的。考虑到编译器的发展方向是更加激进而不是更加理智,并且愿意使用极度扭曲的标准解释来证明它们的行为,我不会相信编译器能够在没有-fno-strict-aliasing的情况下有用地处理上述代码。 - supercat
3
为什么在这个 C 问题中你要引用 C++?请翻译。 - ad absurdum
4
这只是回避问题。为什么有人会在涉及C语言的问题中引用C++标准?想要理解这一点,就必须去查阅正确的标准,以确保他们在这个问题上达成一致。或者也可以选择放弃并说:“嗯,如果它在C++中是真的,那么在C中也一定是真的......” - ad absurdum
2
@davidbowling,我忘记了当我在回答评论两年后的答案时它是C语言,请原谅我的人性。我没有时间或动力去找到正确的引用,您可以改进答案或提供相关的引用或反引用。 - Emily L.
4
为什么需要外部结构体?为什么不能直接将联合体 typedef 为 vec3d_t - Mad Physicist
显示剩余10条评论

13
struct bla {
    struct { int a; int b; };
    int c;
};

类型struct bla有一个成员是C11匿名结构体类型。

struct { int a; int b; }没有标签,对象也没有命名:它是一个匿名结构体类型。

您可以通过以下方式访问匿名结构的成员:

struct bla myobject;
myobject.a = 1;  // a is a member of the anonymous structure inside struct bla   
myobject.b = 2;  // same for b
myobject.c = 3;  // c is a member of the structure struct bla

11
那么,仅仅做"struct bla {int a; int b; int c;}"有何区别? - dhein
4
@Zaibis,访问结构体成员时没有区别,但使用匿名结构的版本会多一个信息:ab之间存在某种逻辑关系,而在使用c时不存在这种关系。请注意不改变原意,并使翻译通俗易懂。 - ouah
你能解释一下这个信息有什么用处吗?这跟性能有关吗?还是说别的方面? - dhein
2
@Zaibis - 内部结构体可以命名并单独使用。一个用例是实现继承(外部结构体扩展内部结构体)。 - martinkunev
1
这样做的好处在于使用位域(bitfields),可以将结构体打包为变长整数,占用与单个int相同的空间。 - Kevin
显示剩余6条评论

8

另一个有用的实现是当你处理rgba颜色时,因为你可能想要分别访问每种颜色或作为单个int。

typedef struct {
    union{
        struct {uint8_t a, b, g, r;};
        uint32_t val;
    };
}Color;

现在您可以访问单独的RGBA值或整个值,其中最高字节为r。例如:

int main(void)
{
    Color x;
    x.r = 0x11;
    x.g = 0xAA;
    x.b = 0xCC;
    x.a = 0xFF;

    printf("%X\n", x.val);

    return 0;
}

打印 11AACCFF


2
也许你只是想展示你能做到这一点,但为什么要使用外部结构体呢?如果你移除外部结构体并将联合体进行typedef,你的代码似乎会表现相同。 - Apprentice_Programmer

5
我不确定为什么C11允许在结构体内部使用匿名结构体。但是Linux在某种语言扩展中使用它:
/**
 * struct blk_mq_ctx - State for a software queue facing the submitting CPUs
 */
struct blk_mq_ctx {
    struct {
        spinlock_t      lock;
        struct list_head    rq_lists[HCTX_MAX_TYPES];
    } ____cacheline_aligned_in_smp;

    /* ... other fields without explicit alignment annotations ... */

} ____cacheline_aligned_in_smp;

我不确定那个例子是否严格必要,除非是为了清晰表达意图。

编辑:我找到了另一个更加明确的类似模式。这个匿名结构体特性与该属性一起使用:

#if defined(RANDSTRUCT_PLUGIN) && !defined(__CHECKER__)
#define __randomize_layout __attribute__((randomize_layout))
#define __no_randomize_layout __attribute__((no_randomize_layout))
/* This anon struct can add padding, so only enable it under randstruct. */
#define randomized_struct_fields_start  struct {
#define randomized_struct_fields_end    } __randomize_layout;
#endif

比如一种语言扩展/编译器插件,可以随机化字段顺序(类似于ASLR风格的漏洞加固):

struct kiocb {
    struct file     *ki_filp;

    /* The 'ki_filp' pointer is shared in a union for aio */
    randomized_struct_fields_start

    loff_t          ki_pos;
    void (*ki_complete)(struct kiocb *iocb, long ret, long ret2);
    void            *private;
    int         ki_flags;
    u16         ki_hint;
    u16         ki_ioprio; /* See linux/ioprio.h */
    unsigned int        ki_cookie; /* for ->iopoll */

    randomized_struct_fields_end
};

0
如果你在代码中只声明了那个结构体的变量一次,为什么它还需要一个名称呢?
struct {
 int a;
 struct {
  int b;
  int c;
 } d;
} e,f;

现在你可以编写像e.af.d.b等的内容。

(我添加了内部结构,因为我认为这是匿名结构最常用的用法之一)


1
这个回答是正确的,而且比我接受的那个提交得早一些。抱歉,那个回答解释得更好一些,但是现在我理解了,我认为这是一个相当不错的答案。 - griotspeak
6
这不是正在讨论的特性,代码中也没有使用任何C11的新功能。示例中的结构体不是匿名的:它们分别有名称.def。它们具有匿名类型,但这是另一回事。 - Alex Celeste
匿名结构体没有标识符和标签。 - Undefined Behavior

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