如何使用匿名结构体/联合体编译C代码?

70
我可以用C++/g++来做这个。
struct vec3 { 
    union {
        struct {
            float x, y, z;
        }; 
        float xyz[3];
    }; 
};

然后,
vec3 v;
assert(&v.xyz[0] == &v.x);
assert(&v.xyz[1] == &v.y);
assert(&v.xyz[2] == &v.z);

会工作。
如何在使用gcc的C语言中实现这个?我有。
typedef struct {
    union {
        struct {
            float x, y, z;
        };
        float xyz[3];
    };
} Vector3;

但是我到处都遇到错误,尤其是在这个地方。
line 5: warning: declaration does not declare anything
line 7: warning: declaration does not declare anything

2
иҜ·дҪҝз”Ё-WallйҮҚж–°зј–иҜ‘жӮЁзҡ„д»Јз ҒгҖӮGCCеә”иҜҘдјҡиӯҰе‘ҠжӮЁжңүе…ійқһеҸҜ移жӨҚеҢҝеҗҚз»“жһ„зҡ„й—®йўҳгҖӮ - greyfade
4
即使在C++中,这也是一个非常糟糕的想法,并且不能保证奏效。 - sellibitze
43
这段话的意思是:我不确定应该把这段内容放在哪里,但是匿名结构体和共用体是C11标准的一部分。因此,当下面的注释说这是一个非标准的GNU扩展时,那已经过时了。 - bk.
9
你在说什么?原则上这不是一个坏主意。他只是创建了一个联合体,在联合体内放置了一个结构体和一个数组。他希望它们是匿名的,以减少成员访问的长度。 - bobobobo
@solinent 只有两个评论:你不需要外部结构体(应该写成 union vec3 { ... }),而且你可能应该将 xyz 成员命名为其他名称,比如 ecomps - bobobobo
@bobobobo 我喜欢把 xyz 看作是一种 swizzle 访问方式,就像 GLSL 一样,即使这并没有太多意义。不过现在我可能会使用 data。我主要使用 C++,所以我会避免使用整个东西,而是使用一个返回数据成员引用的函数,以及用于索引访问的 operator[] - solinent
10个回答

54

13
gcc 4.6可以使用-std=c1x启用这个特性,gcc 4.7及以上版本可以使用-std=c11来启用。 - lambdapower

33

(本回答适用于C99,不适用于C11。)

C99没有匿名结构体或联合体。您必须为它们命名:

typedef struct {
    union {
        struct {
            float x, y, z;
        } individual;
        float xyz[3];
    } data;
} Vector3;

然后在访问它们时,您必须使用名称:

assert(&v.data.xyz[0] == &v.data.individual.x);
在这种情况下,由于您的顶级结构仅具有一个类型为union的项目,因此您可以简化如下:
typedef union {
    struct {
        float x, y, z;
    } individual;
    float xyz[3];
} Vector3;

现在访问数据变成了:

assert(&v.xyz[0] == &v.individual.x);

有没有我可以使用的gcc特定扩展? - solinent
3
GNU C方言支持匿名结构体和联合体。 - David Grayson

25
新的C11标准将支持匿名结构体和联合体,详见2011年4月草案中的前言第6段。 http://en.wikipedia.org/wiki/C1X 奇怪的是,即使在C89和C99模式下,gcc和clang现在也支持匿名结构体和联合体。在我的机器上没有出现任何警告。

7
-pedantic 标志可以捕获这个问题。 - Norswap

13

你也可以始终执行以下操作:

typedef struct
{
    float xyz[0];
    float x, y, z;
}Vec3;

零长度数组不分配任何存储空间,只是告诉C语言“指向下一个声明的东西”。然后,您可以像访问任何其他数组一样访问它:

int main(int argc, char** argv)
{
    Vec3 tVec;
    for(int i = 0; i < 3; ++i)
    {
        tVec.xyz[i] = (float)i;
    }

    printf("vec.x == %f\n", tVec.x);
    printf("vec.y == %f\n", tVec.y);
    printf("vec.z == %f\n", tVec.z);

    return 0;
}

结果:

vec.x == 0.000000
vec.y == 1.000000
vec.z == 2.000000

如果你想更加谨慎,你可以手动指定数据打包策略以适应你的平台。


1
非常有创意,但我的编译器(VS120)抱怨零大小数组是一个非标准的扩展。其他任何编译器都应该警告或者不编译这段代码。 - Johannes Jendersie
如果你在gcc或clang中使用'-std=gnuXX'开关进行编译,这不会产生错误或警告,因为你告诉编译器你知道它是一个扩展。但是,在完全符合标准的C代码中,我会选择使用union。 - Ionoclast Brigham
1
C99 支持可变大小的数组,因此在 C99 中(-std=c99),只需声明为 float[],无需再使用结构体黑客技巧。 - Alex
4
实际上,我撒了谎。如果你将 float[0] (gnu99) 改为 float[] (c99),它不会编译通过,因为可变长度数组必须位于结构体的末尾,在这种情况下没有任何意义。所以还是使用 float[0]。 - Alex

8

匿名联合是C++语言的一个特性。C语言没有匿名联合。

在C和C++中都不存在匿名结构体。

你在问题中提出的声明可能会在GCC C++编译器中编译,但这只是一个编译器特定的扩展,与标准C和标准C++无关。

此外,无论如何实现,C和C++语言都不能保证你的断言是正确的。


1
作为旁注,gcc支持这个扩展,但你需要在非标准C模式下运行gcc(默认情况),或者显式地使用-std = gnu99或类似选项。 - nos
是的,我知道这一点,并且应该提到它。这只是让代码看起来更好,如果它不可移植,也不难修复。在这种情况下,我只是为了自己的使用而编写它,所以这不是一个问题。(我正在编写一个C语言光线追踪器,以学习C语言的复杂性) - solinent

3

我可以在GCC中完成这项任务而不会收到警告。

typedef union {
    struct { // human-friendly access
        float x;
        float y;
        float z;
        float w;
    };
    float xyz[3];
    struct { // human-friendly access
        float r;
        float g;
        float b;
        float a;
    };
    float rgb[3];
} Vector4f;

int main()
{
    Vector4f position, normal, color;
    // human-friendly access
    position.x = 12.3f;
    position.y = 2.f;
    position.z = 3.f;
    position.w = 1.f;

    normal.x = .8f;
    normal.y = .9f;
    normal.z = .1f;
    normal.w = 1.f;

    color.r = 1.f;
    color.g = .233f;
    color.b = 2.11f;
    color.a = 1.1f;

    // computer friendly access
    //some_processor_specific_operation(position.vec,normal.vec);
    return 0;
}

C:\>gcc vec.c -Wall

C:\>gcc --version gcc (GCC) 4.4.0 版权所有 (C) 2009 Free Software Foundation, Inc. 这是自由软件;请查看源代码以了解复制条件。没有保证;甚至不保证适合特定用途。


仍然使用扩展。在命令行中输入-pedantic: "main.cpp:7: warning: ISO C++ prohibits anonymous structs main.cpp:14: warning: ISO C++ prohibits anonymous structs" - GManNickG
2
嗯,问题是关于GCC,而不是ISO C ++..虽然知道ISO C ++的说法也很好..; P - Afriza N. Arief

2

匿名联合在C语言中不受支持。

同时需要注意,如果您以这种方式声明:

typedef struct {
    union {
        struct {
            float x, y, z;
        } individual;
        float xyz[3];
    } data;
} Vector3;

进行中

Vector3 v;
v.data.xyz[0] = 5;

float foo = v.data.individual.x;

这是一种未定义的行为。你只能访问最后一个被赋值的联合成员。在你的情况下,使用联合是错误的编程实践,因为它依赖于许多标准中未指定的东西(如填充...)。

在C语言中,你会更喜欢像这样的写法:

typedef struct {
    float v[3];
} Vec3;

如果您不想使用v[x],您可以考虑以下方法:

#define X(V) ((V).v[0])

Vec3 v;
X(v) = 5.3;
printf("%f\n", X(v));

3
标准规定,当一个工会成员被分配时,其他成员的值为未指定的。同时,它还规定了这些成员所共享的位表示。这并不是未定义的行为,看起来非常明确定义了。 - greyfade
标准规定:“当一个值被存储在联合类型对象的成员中时,对象表示的字节不对应于该成员但对应于其他成员的字节取未指定值。” 但这并不意味着其他成员的值可能不是陷阱(它们的复合字节不是,仅此而已)。它说:“结构或联合对象的值永远不是陷阱表示,即使结构或联合对象的成员的值可能是陷阱表示。”从不同的成员读取本身并不是未定义行为,但可能会是。 - Johannes Schaub - litb
1
如果我们读取了一个陷阱表示,那么行为是未定义的,正如一个脚注(非规范性)所说得那样:“如果用于访问联合对象内容的成员与上次用于在对象中存储值的成员不同,则该值的对象表示的适当部分将被重新解释为新类型的对象表示,如6.2.6所述(有时称为“类型游戏”)。这可能是一个陷阱表示。” - Johannes Schaub - litb

0

GNU C方言支持匿名结构/联合体,但默认情况下GCC使用某种标准C进行编译。要使用GNU方言,请在命令行上加入“-std = gnu99”。


0

未标识的结构成员不符合ANSI/ISO C99标准,但我发现有趣的事情发生了,在某些GNU C编译器2.x.x版本的端口上,使用未标识的结构成员是有效的,它能够找到它们,不会说类似“x不是联合体\结构体y的成员,x是什么?”这样的话,其他时候则会出现“x未定义”、“x不是结构体的成员”,甚至我发誓曾经看到过“指向未知”的情况,由于这个原因。

所以,作为专业人士,我会和其他人一样,给结构体\联合体成员一个标识符,或者在联合体的情况下,仔细重新排列代码,使联合体成为已知结构体的已知成员,并且嵌入在原始联合体的未标识结构中的成员成为已知结构体的成员,并且与已知联合体成员一起谨慎使用。但在那些后一种方法不可行的情况下,我会给匿名结构体一个标识符并继续进行。


-1

我可以建议一个有趣的解决方法,以避免结构中有太多字段。建议警告简单命名的定义,因为它可能会创建冲突。

#define x    ___fl_fld[0]
#define y    ___fl_fld[1]
#define z    ___fl_fld[2]
#define w    ___fl_fld[3]
#define r    ___fl_fld[0]
#define g    ___fl_fld[1]
#define b    ___fl_fld[2]
#define a    ___fl_fld[3]
typedef union {
    float ___fl_fld[4];
    float xyz[3];
    float rgb[3];
} Vector3;

您可以像这样访问结构:
Vector3 v;
assert(&v.x == &v.r); //Should return true

最后,这将是一个与C99兼容的多类型联合体:

#define u8llsb __u8[0]
#define u8lmsb __u8[1]
#define u8mlsb __u8[2]
#define u8mmsb __u8[3]
#define u16lsb __u16[0]
#define u16msb __u16[1]
#define u16    __u16[0]
#define u8lsb  __u8[0]
#define u8msb  __u8[1]

typedef union {
    uint32_t u32;
    int32_t  i32;
    uint16_t  __u16[2];
    uint8_t   __u8[4];
} multitype_t;

multitype_t Var;
var.u32;
var.i32;
var.u8llsb;
/* etc. */

我只能想象,如果将所有单字符变量名称定义为“__fl_fld [2]”,那么编译器会产生多么惊人的编译错误。 - StilesCrisis
将比特转换为可读的形式,用通用名称替换除混淆代码竞赛条目以外的任何定义... - fgp
这就是为什么许多编码标准不鼓励或禁止使用宏的原因。个人认为,如果合理使用,预处理器宏可以极大地提高代码质量。但问题在于,您正在污染全局命名空间。如果任何人包含了一个包含这些#定义的头文件,并且他们碰巧在任何地方都有一个名为“x”或“y”等的变量,那么您已经破坏了他们的代码。 - Die in Sente

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