14

The below code can be compiled successfully in Visual C++. I like it and it is sweet!

#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;
}

Output with the file built with MSC:

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

But in GCC, including the latest gcc-7, it failed to be compiled, :(

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

Is it a bug in GCC?


Thanks for your comments, I just understood this question may be invalid for C; But for C++ part, I still have the concern. Personally I like Visual C++ compile behavior, it can save tons of code in my scenario

jjmontes
  • 24,679
  • 4
  • 39
  • 51
ravin.wang
  • 1,122
  • 1
  • 9
  • 26

3 Answers3

28

6.7.2.1 Structure and union specifiers says:

An unnamed member whose type specifier is a structure specifier with no tag is called an anonymous structure; an unnamed member whose type specifier is a union specifier with no tag is called an anonymous union. The members of an anonymous structure or union are considered to be members of the containing structure or union. This applies recursively if the containing structure or union is also anonymous.

(emphasis mine)

So based on that, it's essentially as if you had:

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

which would obviosuly be invalid and gcc correctly issues diagnostics.

P.P
  • 117,907
  • 20
  • 175
  • 238
  • 2
    That is for C, for C++, anonymous struct are not standard, but unnamed are: [anonymous/unnamed struct](https://stackoverflow.com/a/14248127/5632316). – Oliv Apr 04 '18 at 08:39
  • For C, your explanation is perfect for me;but for C++, the situation seems to be different, anonymous structure is still a structure, and compiler will give a name during compile time. – ravin.wang Apr 04 '18 at 08:45
  • 2
    @ravin.wang As the post linked by Oliv says, it's non-standard in C++. So whatever MSVC does to allow that is non-standard too (if not a bug). – P.P Apr 04 '18 at 08:52
  • @P.P. Seems so, hopefully C++ ISO org can adopt the behavior implemented by MSFT, :) – ravin.wang Apr 04 '18 at 08:54
  • 6
    The OP code is a union of 2 structs, not the union of all members of inner structs. – Frankie_C Apr 04 '18 at 10:59
  • 2
    @Frankie_C Not just union of "2 structs" but 2 *anonymous* structs - which becomes problematic for the reason stated (as the quoted from the standard). If the inner structs have tags then the problem doesn't arise. – P.P Apr 04 '18 at 11:11
  • 4
    The problem isn't the unique names required for fields of nameless structures/unions, but the fact that you transformed the **union of 2 nameless structures** in a **union of all fields** of any object present.. – Frankie_C Apr 04 '18 at 11:26
19

It's not a bug in GCC.

The language standard does not allow this. But Visual C++ does, if anything to allow compilation of Windows headers. In fact, if you don't want to use a Microsoft compiler to compile Windows headers then you need to use

#define NONAMELESSUNION

before #include <windows.h>. It probably seemed like a good idea at the time.

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

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Bathsheba
  • 231,907
  • 34
  • 361
  • 483
  • But I think the behavior in MSC is much smarter, because field a/b has the same bits, and are also aligned in memory, in my scenario, I need this behavior, it will save lots of effort in my code. – ravin.wang Apr 04 '18 at 08:22
  • 1
    I think this answer is saying C doesn't support anonymous unions at all. If so, it's wrong. If not, I can't see the relevance of the second half of this answer. –  Apr 04 '18 at 08:25
  • 1
    C11:6.7.2.1/13 "The members of an anonymous structure or union are considered to be members of the containing structure or union." So I do not see how they could have the same name. – Mihayl Apr 04 '18 at 08:32
  • 6
    @ravin.wang: I don't see how this "saves lots of effort". You are duplicating fields, which is error prone and time consuming. If these fields are not a part of the union, then don't copy/paste them but simply get them out of the union. – vgru Apr 04 '18 at 08:59
  • @Groo The scenario in my case, I am parsing multiple types of stream, most of fields are the same for them, but only some fields are different, I don't want to separate the cases, and write the duplicated code for each cases, in MSC, it can save my code effort with anonymous structure in union, when porting it gcc, I have to change anonymous structure back to named structure, and change lots of code, it is really painful for me in gcc. – ravin.wang Apr 04 '18 at 09:04
  • 5
    @ravin.wang even in your case, the two anonymous structs are guaranteed to have the same bit pattern, but how the individual members (`a`, `b`, `c1`) are arranged is implementation-defined. So you cannot say that `a` from both the structs will be exactly the same. – Ajay Brahmakshatriya Apr 04 '18 at 09:15
  • 1
    @ravin.wang: bitfield layout is completely compiler-dependant. Using bitfields and relying on struct packing and endianness are exactly opposite to writing portable C parsers - even a new version of your favorite compiler, or simply changing optimization settings might rearrange these fields. Even if you're lucky, you lose any chance of protocol versioning (i.e. "I want to support the v2 protocol which is exactly the same as v1, but has a tiny additional byte in the middle of one struct. So now I need to create new versions of all my structs, leading to even more duplicated code"). – vgru Apr 04 '18 at 09:17
  • @AjayBrahmakshatriya At first, when wring this kind of code, I can guarantee the structure is packed, and compiler can't optimize it. Second, for the fields with the same pattern can also be guaranteed by me when I using this feature, MSC can handle it well in my scenario from VC6 to VC15. – ravin.wang Apr 04 '18 at 09:27
  • 1
    *MSC can handle* -- doesn't mean anything. One compiler showing some behavior doesn't guarantee anything. You will soon run into portability issues. Just like you already have with `gcc`. – Ajay Brahmakshatriya Apr 04 '18 at 09:32
  • @AjayBrahmakshatriya I refined my sample code which is closer to my original code. – ravin.wang Apr 04 '18 at 09:42
  • 1
    @ravin.wang: struct packing is not a C feature and [bitfield ordering is implementation defined](https://stackoverflow.com/q/1490092/69809). You are using compiler-specific extensions *and* relying on MSVC's notorious non-conformance to standards *and* suggesting that the standard should be changed to match an incorrect implementation, so that non-portable code would pass compilation. Don't forget to tell the ISO people to enforce that all microcontrollers must be little-endian too. – vgru Apr 04 '18 at 10:26
  • 1
    @Groo: Microsoft did force a lot of things into C99 but I doubt they have so much power these days. Gone are the days of the simply brilliant Windows 95 OS, Office 97, and COM which put Microsoft on such a leading trajectory. As an example of standardisation going the wrong way for Microsoft, note that in C++, the behaviour of things like `i = ++i` will be soon well-defined (currently it's UB), and the definition will be along the lines of how gcc does it, not MSVC. Oops. – Bathsheba Apr 04 '18 at 10:27
  • 1
    @Bathsheba: but the last time I checked, gcc would throw a warning for sequence point UB, and compile differently based on optimization settings. What do you mean by "along the lines of how gcc does it"? – vgru Apr 04 '18 at 10:31
  • @Groo: The generated assembly increments i before the assignment. MSVC increments i at the end of the statement. – Bathsheba Apr 04 '18 at 10:32
  • It's clearly UB, but if I am reading your comment correcly, both GCC and MSVS end up with `i_prev + 1` stored inside `i`? Anyway, I was thinking of something like `*p++ = *p + 5`, I recall this being an example of something that's just a warning in GCC and compiles differently if you enable optimizations. – vgru Apr 04 '18 at 11:03
1

It has been discussed in P.P.'s answer and the comments, that what you want isn't correct and GCC behaves correctly. But an easy workaround which fulfills your need might be that you rename a and b in the second struct:

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;

This worked for me in GCC and Clang and should still allow your trick to work nicely.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
johannes
  • 15,807
  • 3
  • 44
  • 57
  • 3
    Using this code, it’s illegal to access `a` and `d` simultaneously though, because they are in different union members. – Konrad Rudolph Apr 04 '18 at 11:18
  • 1
    @KonradRudolph : What do you mean by "simultaneously" ? – MPW Apr 04 '18 at 13:36
  • 3
    @MPW Without setting a new value for the union member. Given `A a = some_value;`, you cannot legally say `x = a.a; y = a.d;`, even though something similar is a commonly used idiom with its own name, “union cast”. It nevertheless violates strict aliasing and is thus UB. – Konrad Rudolph Apr 04 '18 at 13:53
  • Considering that the post already is using nin-standard packing attributes and bit fiddling making assumptions about the layout I think the implied assumption of this to work is acceptable. – johannes Apr 04 '18 at 14:15
  • 3
    @johannes Hm. On the one hand, yes. On the other hand, OP is explicitly asking about C++ standards compliance. – Konrad Rudolph Apr 04 '18 at 14:56
  • @KonradRudolph Wouldn't it fall under common prefix exception (see 9.5.1)? – Maciej Piechotka Apr 04 '18 at 18:45
  • @KonradRudolph yes, that's why I referenced other answers which do standard lawyering (which I read with interest) – johannes Apr 04 '18 at 18:56
  • @MaciejPiechotka: As of C++14, it would seem so. – cHao Apr 04 '18 at 19:51