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

Posted

技术标签:

【中文标题】如何使用匿名结构/联合编译 C 代码?【英文标题】:How to compile C code with anonymous structs / unions? 【发布时间】:2010-12-30 15:07:03 【问题描述】:

我可以在 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

【问题讨论】:

使用-Wall 再次编译您的代码。 GCC 应该给你关于不可移植匿名结构的警告。 即使在 C++ 中,这也是一个非常糟糕的主意,并且不能保证有效。 我不知道该放在哪里,但匿名结构和联合是 C11 标准的一部分。所以当下面的 cmets 说这是一个非标准的 GNU 扩展时,它现在已经过时了。 @sellibitze 你在说什么?原则上,这不是一个坏主意。他只是创建了一个联合,并在联合内部放置了一个结构和一个数组。他希望他们是匿名的,以减少成员访问的时间。 @solinent 只有 2 个 cmets:你不需要外部结构(你应该写 union vec3 ... ),你应该将 xyz 成员命名为 otherxyzecomps 之类的东西可以正常工作。 【参考方案1】:

根据http://gcc.gnu.org/onlinedocs/gcc/Unnamed-Fields.html#Unnamed-Fields

-fms-extensions 将启用您(和我)想要的功能。

【讨论】:

gcc 4.6 也通过-std=c1x 和 gcc 4.7+ 和-std=c11 启用此功能【参考方案2】:

(此答案适用于 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);

在这种情况下,因为您的***结构有一个联合类型的项目,您可以简化:

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

现在访问数据变成:

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

【讨论】:

我可以使用 gcc 特定的扩展吗? C 的 GNU 方言支持匿名结构和联合。【参考方案3】:

新的 C11 标准将支持匿名结构和联合,请参阅 2011 年 4 月草案的前言第 6 段。

http://en.wikipedia.org/wiki/C1X

奇怪的是 gcc 和 clang 现在都支持 C89 和 C99 模式下的匿名结构和联合。在我的机器上没有出现警告。

【讨论】:

-pedantic 标志会捕捉到这一点。【参考方案4】:

人们还可以随时执行以下操作:

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

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

【讨论】:

非常有创意,但我的编译器 (VS120) 抱怨零大小的数组也是非标准扩展。每个其他编译器也应该警告或不编译此代码。 如果您在 gcc 或 clang 中使用开关 '-std=gnuXX' 进行编译,这不会是错误或警告,因为您告诉编译器您承认它是一个扩展。但是,是的,在完全符合标准的 C 代码中,我宁愿选择联合。 C99 支持可变大小的数组,因此在 C99 (-std=c99) 的情况下,只需声明为 float[],不再需要 struct hack。 其实我在撒谎。如果您将 float[0] (gnu99) 更改为 float[] (c99) - 它不会编译,因为变量数组必须位于结构的末尾,在这种情况下它没有任何意义。所以 float[0] 就是这样。【参考方案5】:

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

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

您在问题中提出的声明可能会使用 GCC C++ 编译器进行编译,但它只是特定于编译器的扩展,与标准 C 和标准 C++ 无关。

最重要的是,无论您如何实现它,C 和 C++ 语言都不能保证您的断言会成立。

【讨论】:

作为旁注,gcc 确实支持将此作为扩展,但您将在非标准 C 模式下运行 gcc(默认),或明确使用 -std=gnu99 或类似的。 是的,我知道这一点,并且应该提到它。它只是使代码看起来更好,并且如果它不可移植也不难修复。在这种情况下,我只是为了自己使用而编写它,所以这不是问题。 (我正在写一个 c 光线追踪器来学习 c 的复杂性)【参考方案6】:

我可以在 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) 4.4.0 版权所有 (C) 2009 Free Software Foundation, Inc. 这是免费软件;查看复制条件的来源。没有 保修单;甚至不考虑适销性或特定用途的适用性。

【讨论】:

仍然使用扩展。在命令行输入-pedantic:“main.cpp:7:warning:ISO C++禁止匿名结构main.cpp:14:warning:ISO C++禁止匿名结构” 好吧,问题是关于 GCC,而不是 ISO C++ .. 很高兴知道 ISO C++ 是怎么说的.. ; P【参考方案7】:

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));

【讨论】:

标准规定,当一个联合的成员被赋值时,其他成员的值是未指定。它还说位表示在成员之间共享。这不是未定义的行为。它似乎定义得很清楚。 标准规定“当一个值存储在联合类型对象的成员中时,对象表示中与该成员不对应但与其他成员对应的字节采用未指定的值。 "但这并不意味着其他成员的值可能不是陷阱(它们的复合字节不是,仅仅是)。它说“结构或联合对象的值永远不是陷阱表示,即使结构或联合对象的成员的值可能是陷阱表示。”。从不同的成员读取本身并不是未定义的行为,但它可能。 .. 如果我们读到一个陷阱表示然后行为是未定义的,这可能是因为它在脚注(非规范性)中最清楚地说:“如果成员曾经访问联合的内容对象与上次用于在对象中存储值的成员不同,值的对象表示的适当部分被重新解释为新类型中的对象表示,如 6.2.6 所述(有时称为“类型双关语“)。这可能是一个陷阱表示。”。【参考方案8】:

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

【讨论】:

【参考方案9】:

未识别的结构成员不是 ANSI/ISO C99 标准解释了这一点,但我发现发生了一件有趣的事情,在 GNU C 编译器 2.xx 版本的某些端口上,使用未识别的结构成员有效,它找到了它们,没有说诸如“x 不是 union\struct y 的成员,x 是什么?”之类的东西,其他时候,它是 ol'“x 未定义”,“x 不是 struct 的成员”,我发誓我看到了一个“指向未知的指针”,因为这个原因。

所以我,专业地会和其他人一起去,只是给结构\联合成员一个标识符,或者在联合的情况下,仔细重新排列代码,使联合最终成为一个已识别结构的已识别成员,并且嵌入原始联合的未识别结构中的成员成为已识别结构的成员,并与已识别的联合成员一起仔细使用。 但在那些情况下,如果后一种方法不是可行的替代品,我只会给烦人的结构一个标识符,然后继续。

【讨论】:

【参考方案10】:

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

#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. */

【讨论】:

我只能想象在#define将所有单字符变量名输入__fl_fld[2]之后会出现惊人的编译器错误。 混淆代码竞赛的可读性位,以及混淆代码竞赛条目中定义的通用名称的方式... 这就是为什么许多编码标准不鼓励或禁止使用宏的原因。就个人而言,我认为预处理器宏名声不好。如果使用得当,它们可以大大提高代码质量。但这里的问题是你正在污染全局命名空间。如果有人包含包含这些#definitions 的头文件,并且他们碰巧在任何地方都有一个名为“x”或“y”等的变量,那么你已经破坏了他们的代码。

以上是关于如何使用匿名结构/联合编译 C 代码?的主要内容,如果未能解决你的问题,请参考以下文章

C语言中的匿名结构体

C语言中的匿名结构体

C++匿名联合重新声明错误

c#/c++ 如何将结构的结构与 marshal.sizeof 联合

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

如何将结构从 Matlab 代码转换为 C 代码(使用 Matlab 编译器)