【问题标题】:Are identically named members of unnamed structs in a union an error or a GCC bug?联合中未命名结构的同名成员是错误还是 GCC 错误?
【发布时间】:2018-09-13 17:07:01
【问题描述】:

下面的代码可以在Visual C++编译成功。我喜欢它,它很甜!

#include <stdio.h>

#ifdef _MSC_VER
    #pragma warning(push)
    #pragma warning(disable:4201)
    #pragma pack(push,1)
    #define PACKED
#else
    #define PACKED __attribute__ ((__packed__))
#endif

union A {
    struct {
        int a:1;
        int b:2;
        int c1:29;
    }PACKED;
    struct {
        int a:1;
        int b:2;
        int c2:28;
        int d:1;
    }PACKED;
    int val;
}PACKED;

#ifdef _MSC_VER
    #pragma pack(pop)
    #pragma warning(pop)
#endif
#undef PACKED

int main(){
    A test;
    test.val = 0x1078FFF7;
    printf("sizeof(A): %d, test.a: %d.\n", sizeof(A), test.a);
    return -1;
}

使用 MSC 构建的文件输出:

sizeof(A): 4, test.a: -1.

但是在GCC,包括最新的gcc-7,编译失败,:(

struct.cpp:13:15: error: redeclaration of ‘signed char:1 A::<unnamed struct>::a’
     int a:1;
           ^
struct.cpp:7:15: note: previous declaration ‘signed char:1 A::<unnamed struct>::a’
     int a:1;
           ^
struct.cpp:14:15: error: redeclaration of ‘signed char:2 A::<unnamed struct>::b’
     int b:2;
           ^
struct.cpp:8:15: note: previous declaration ‘signed char:2 A::<unnamed struct>::b’
     int b:2;
           ^

这是 GCC 中的错误吗?


感谢您的cmets,我刚刚明白这个问题可能对C无效;但对于 C++ 部分,我仍然有顾虑。我个人喜欢 Visual C++ 编译行为,它可以在我的场景中节省大量代码

【问题讨论】:

  • 您将如何访问您的字段,因为它们在 2 个匿名结构中具有相同的名称?
  • 编译器无法区分第一个a和第二个。
  • 我会说是 MSC 中的一个错误让它过去了。
  • C 和 C++ 中重新声明的规则非常不同。在文件范围内,int c; int c; 是有效的 C,无效的 C++。请修正您问题上的标签,只询问您感兴趣的语言。
  • gcc 可能试图尊重 C 标准,MSC 根本不在乎。

标签: c++ gcc language-lawyer


【解决方案1】:

6.7.2.1 Structure and union specifiers 说:

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

(强调我的)

因此,基于此,基本上就像您拥有:

union A
{
    int a:1;
    int b:2;
    int c1:29;

    int a:1;
    int b:2;
    int c2:28;
    int d:1;

    int val;
};

这显然是无效的,并且 gcc 会正确发出诊断信息。

【讨论】:

  • 即对于C,对于C++,匿名结构不是标准的,但未命名的是:anonymous/unnamed struct
  • @ravin.wang 正如 Oliv 链接的帖子所说,它在 C++ 中是非标准的。所以无论 MSVC 做什么来允许它也是非标准的(如果不是错误的话)。
  • OP 代码是 2 个结构的并集,而不是内部结构的所有成员的并集。
  • @Frankie_C 不仅是“2 个结构”的联合,还有 2 个 anonymous 结构 - 由于所述原因(如标准中引用的那样),这变得有问题。如果内部结构有标签,那么问题就不会出现。
  • 问题不在于无名结构/联合的字段所需的唯一名称,而是您在 联合中转换了 2 个无名结构的联合存在的任何对象的所有字段..
【解决方案2】:

这不是 GCC 中的错误。

语言标准不允许这样做。但是 Visual C++ 确实允许编译 Windows 头文件。事实上,如果你不想使用微软编译器来编译 Windows 头文件,那么你需要使用

#define NONAMELESSUNION

#include &lt;windows.h&gt; 之前。这在当时似乎是个好主意。

参考:What are anonymous structs, and more importantly, how do I tell windows.h to stop using them?

【讨论】:

  • 我认为这个答案是说 C 根本不支持匿名联合。如果是这样,那就错了。如果不是,我看不出这个答案后半部分的相关性。
  • C11:6.7.2.1/13 “匿名结构或联合的成员被视为包含结构或联合的成员。”所以我不明白他们怎么会有相同的名字。
  • @ravin.wang:我不明白这如何“节省大量精力”。您正在复制字段,这容易出错且耗时。如果这些字段不是联合的一部分,则不要复制/粘贴它们,只需将它们从联合中取出即可。
  • @ravin.wang 即使在您的情况下,这两个匿名结构也保证具有相同的位模式,但是各个成员(abc1)的排列方式是实现定义的。所以你不能说两个结构中的a 完全相同。
  • @ravin.wang:位域布局完全依赖于编译器。使用位域并依赖结构打包和字节序与编写可移植的 C 解析器完全相反——即使是您最喜欢的编译器的新版本,或者只是更改优化设置都可能重新排列这些字段。即使你很幸运,你也会失去任何协议版本控制的机会(即“我想支持与 v1 完全相同的 v2 协议,但在一个结构中间有一个很小的额外字节。所以现在我需要为我的所有结构创建新版本,从而导致更多重复代码")。
【解决方案3】:

已经在 P.P. 的回答和 cmets 中讨论过,您想要的不正确,而 GCC 的行为正确。但是满足您需求的一个简单解决方法可能是您在第二个结构中重命名 ab

union A {
    struct {
        int a:1;
        int b:2;
        int c1:29;
    }PACKED;
    struct {
        int unused_name_a:1;
        int unused_name_b:2;
        int c2:28;
        int d:1;
    }PACKED;
    int val;
}PACKED;

这在 GCC 和 Clang 中对我有用,并且仍然可以让你的技巧很好地发挥作用。

【讨论】:

  • 使用此代码,同时访问ad是非法的,因为它们在不同的工会成员中。
  • @KonradRudolph:“同时”是什么意思?
  • @MPW 没有为联合成员设置新值。给定A a = some_value;,你不能合法地说x = a.a; y = a.d;,尽管类似的东西是一个常用的成语,有自己的名字,“联合演员”。尽管如此,它还是违反了严格的别名,因此是 UB。
  • 考虑到该帖子已经在使用 nin-standard 包装属性并且有点摆弄对布局的假设,我认为隐含的假设是可以接受的。
  • @johannes 嗯。一方面,是的。另一方面,OP 明确询问 C++ 标准的合规性。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多