ChatGPT解决这个技术问题 Extra ChatGPT

Purpose of Unions in C and C++

I have used unions earlier comfortably; today I was alarmed when I read this post and came to know that this code

union ARGB
{
    uint32_t colour;

    struct componentsTag
    {
        uint8_t b;
        uint8_t g;
        uint8_t r;
        uint8_t a;
    } components;

} pixel;

pixel.colour = 0xff040201;  // ARGB::colour is the active member from now on

// somewhere down the line, without any edit to pixel

if(pixel.components.a)      // accessing the non-active member ARGB::components

is actually undefined behaviour I.e. reading from a member of the union other than the one recently written to leads to undefined behaviour. If this isn't the intended usage of unions, what is? Can some one please explain it elaborately?

Update:

I wanted to clarify a few things in hindsight.

The answer to the question isn't the same for C and C++; my ignorant younger self tagged it as both C and C++.

After scouring through C++11's standard I couldn't conclusively say that it calls out accessing/inspecting a non-active union member is undefined/unspecified/implementation-defined. All I could find was §9.5/1: If a standard-layout union contains several standard-layout structs that share a common initial sequence, and if an object of this standard-layout union type contains one of the standard-layout structs, it is permitted to inspect the common initial sequence of any of standard-layout struct members. §9.2/19: Two standard-layout structs share a common initial sequence if corresponding members have layout-compatible types and either neither member is a bit-field or both are bit-fields with the same width for a sequence of one or more initial members.

While in C, (C99 TC3 - DR 283 onwards) it's legal to do so (thanks to Pascal Cuoq for bringing this up). However, attempting to do it can still lead to undefined behavior, if the value read happens to be invalid (so called "trap representation") for the type it is read through. Otherwise, the value read is implementation defined.

C89/90 called this out under unspecified behavior (Annex J) and K&R's book says it's implementation defined. Quote from K&R: This is the purpose of a union - a single variable that can legitimately hold any of one of several types. [...] so long as the usage is consistent: the type retrieved must be the type most recently stored. It is the programmer's responsibility to keep track of which type is currently stored in a union; the results are implementation-dependent if something is stored as one type and extracted as another.

Extract from Stroustrup's TC++PL (emphasis mine) Use of unions can be essential for compatness of data [...] sometimes misused for "type conversion".

Above all, this question (whose title remains unchanged since my ask) was posed with an intention of understanding the purpose of unions AND not on what the standard allows E.g. Using inheritance for code reuse is, of course, allowed by the C++ standard, but it wasn't the purpose or the original intention of introducing inheritance as a C++ language feature. This is the reason Andrey's answer continues to remain as the accepted one.

Simply stated, compilers are allowed to insert padding between elements in a structure. Thus, b, g, r, and a may not be contiguous, and thus not match the layout of a uint32_t. This is in addition to the Endianess issues that others have pointed out.
This is exactly why you shouldn't tags questions C and C++. The answers are different, but since answerers do not even tell for what tag they are answering (do they even know?), you get rubbish.
@downvoter Thanks for not explaining, I understand that you want me to magically understand your gripe and not repeat it in future :P
Regarding the original intention of having union, bear in mind that the C standard post-dates C unions by several years. A quick look at Unix V7 shows a few type conversions via unions.
scouring C++11's standard I couldn't conclusively say that it calls out accessing/inspecting a non-active union member is undefined [...] All I could find was §9.5/1 ...really? you quote an exception note, not the main point right at the start of the paragraph: "In a union, at most one of the non-static data members can be active at any time, that is, the value of at most one of the non-static data members can be stored in a union at any time." - and down to p4: "In general, one must use explicit destructor calls and placement new operators to change the active member of a union"

J
Juan Carlos Ramirez

The purpose of unions is rather obvious, but for some reason people miss it quite often.

The purpose of union is to save memory by using the same memory region for storing different objects at different times. That's it.

It is like a room in a hotel. Different people live in it for non-overlapping periods of time. These people never meet, and generally don't know anything about each other. By properly managing the time-sharing of the rooms (i.e. by making sure different people don't get assigned to one room at the same time), a relatively small hotel can provide accommodations to a relatively large number of people, which is what hotels are for.

That's exactly what union does. If you know that several objects in your program hold values with non-overlapping value-lifetimes, then you can "merge" these objects into a union and thus save memory. Just like a hotel room has at most one "active" tenant at each moment of time, a union has at most one "active" member at each moment of program time. Only the "active" member can be read. By writing into other member you switch the "active" status to that other member.

For some reason, this original purpose of the union got "overridden" with something completely different: writing one member of a union and then inspecting it through another member. This kind of memory reinterpretation (aka "type punning") is not a valid use of unions. It generally leads to undefined behavior is described as producing implementation-defined behavior in C89/90.

EDIT: Using unions for the purposes of type punning (i.e. writing one member and then reading another) was given a more detailed definition in one of the Technical Corrigenda to the C99 standard (see DR#257 and DR#283). However, keep in mind that formally this does not protect you from running into undefined behavior by attempting to read a trap representation.


+1 for being elaborate, giving a simple practical example and saying about the legacy of unions!
The problem I have with this answer is that most OSes I have seen have header files that do this exact thing. For example I've seen it in old (pre-64-bit) versions of <time.h> on both Windows and Unix. Dismissing it as "not valid" and "undefined" isn't really sufficient if I'm going to be called upon to understand code that works in this exact way.
@AndreyT “It has never been legal to use unions for type punning until very recently”: 2004 is not “very recent”, especially considering that it is only C99 that was initially clumsily worded, appearing to make type-punning through unions undefined. In reality, type-punning though unions is legal in C89, legal in C11, and it was legal in C99 all along although it took until 2004 for the committee to fix incorrect wording, and the subsequent release of TC3. open-std.org/jtc1/sc22/wg14/www/docs/dr_283.htm
@legends2k Programming language are defined by standard. The Technical Corrigendum 3 of the C99 standard explicitly allows type-punning in its footnote 82, which I invite you to read for yourself. This is not TV where rock stars are interviewed and express their opinions on climate change. Stroustrup's opinion has zero influence on what the C standard says.
@legends2k "I know that any individual's opinion doesn't matter and only the standard does" The opinion of compiler writers matters a lot more than the (extremely poor) language "specification".
E
Erich Kitzmueller

You could use unions to create structs like the following, which contains a field that tells us which component of the union is actually used:

struct VAROBJECT
{
    enum o_t { Int, Double, String } objectType;

    union
    {
        int intValue;
        double dblValue;
        char *strValue;
    } value;
} object;

I totally agree, without entering the undefined-behaviour chaos, perhaps this is the best intended behaviour of unions I can think of; but won't is waste space when am just using, say int or char* for 10 items of object[]; in which case, I can actually declare separate structs for each data type instead of VAROBJECT? Wouldn't it reduce clutter and use lesser space?
legends: In some cases, you simply can't do that. You use something like VAROBJECT in C in the same cases when you use Object in Java.
The data structure of tagged unions seems to be a only legitimate use of unions, as you explain.
Also give an example of how to use the values.
@CiroSantilli新疆改造中心六四事件法轮功 A part of an example from C++ Primer, might help. wandbox.org/permlink/cFSrXyG02vOSdBk2
D
David Rodríguez - dribeas

The behavior is undefined from the language point of view. Consider that different platforms can have different constraints in memory alignment and endianness. The code in a big endian versus a little endian machine will update the values in the struct differently. Fixing the behavior in the language would require all implementations to use the same endianness (and memory alignment constraints...) limiting use.

If you are using C++ (you are using two tags) and you really care about portability, then you can just use the struct and provide a setter that takes the uint32_t and sets the fields appropriately through bitmask operations. The same can be done in C with a function.

Edit: I was expecting AProgrammer to write down an answer to vote and close this one. As some comments have pointed out, endianness is dealt in other parts of the standard by letting each implementation decide what to do, and alignment and padding can also be handled differently. Now, the strict aliasing rules that AProgrammer implicitly refers to are a important point here. The compiler is allowed to make assumptions on the modification (or lack of modification) of variables. In the case of the union, the compiler could reorder instructions and move the read of each color component over the write to the colour variable.


+1 for the clear and simple reply! I agree, for portability, the method you've given in the 2nd para holds good; but can I use the way I've put up in the question, if my code is tied down to a single architecture (paying the price of protability), since it saves 4 bytes for each pixel value and some time saved in running that function?
@legends2k, the problem is that optimizer may assume that an uint32_t is not modified by writing to a uint8_t and so you get the wrong value when the optimized use that assumption... @Joe, the undefined behavior appears as soon as you access the pointer (I know, there are some exceptions).
@legends2k/AProgrammer: The result of a reinterpret_cast is implementation defined. Using the pointer returned does not result in undefined behaviour, only in implementation defined behaviour. In other words, the behaviour must be consistant and defined, but it isn't portable.
@legends2k: any decent optimizer will recognize bitwise operations that select an entire byte and generate code to read/write the byte, same as the union but well-defined (and portable). e.g. uint8_t getRed() const { return colour & 0x000000FF; } void setRed(uint8_t r) { colour = (colour & ~0x000000FF) | r; }
@curiousguy: The Standard specifies that if sizeof (sometype) reports N, then converting a pointer to that type to char* and reading N values will yield some (not necessarily unique) sequence of unsigned char values. It also specifies that overwriting an object with such a sequence of char values will set its value to the value that would have yielded that sequence. The Standard could have specified that a union most behave as though it holds a sequence of unsigned char values, and the effects of reading and writing would be defined in terms of the effects on those values.
b
bobobobo

The most common use of union I regularly come across is aliasing.

Consider the following:

union Vector3f
{
  struct{ float x,y,z ; } ;
  float elts[3];
}

What does this do? It allows clean, neat access of a Vector3f vec;'s members by either name:

vec.x=vec.y=vec.z=1.f ;

or by integer access into the array

for( int i = 0 ; i < 3 ; i++ )
  vec.elts[i]=1.f;

In some cases, accessing by name is the clearest thing you can do. In other cases, especially when the axis is chosen programmatically, the easier thing to do is to access the axis by numerical index - 0 for x, 1 for y, and 2 for z.


This is also called type-punning which is also mentioned in the question. Also the example in the question shows a similar example.
It's not type punning. In my example the types match, so there is no "pun", it's merely aliasing.
Yes, but still, from an absolute viewpoint of the language standard, the member written to and read from are different, which is undefined as mentioned in the question.
I would hope that a future standard would fix this particular case to be allowed under the "common initial subsequence" rule. However, arrays do not participate in that rule under the current wording.
@curiousguy: There is clearly no requirement that the structure members be placed without arbitrary padding. If code tests for structure-member placement or structure size, code should work if accesses are done directly through the union, but a strict reading of the Standard would indicate that taking the address of a union or struct member yields a pointer which cannot be used as a pointer of its own type, but must first be converted back to a pointer to the enclosing type or a character type. Any remotely-workable compiler will extend the language by making more things work than...
佚名

As you say, this is strictly undefined behaviour, though it will "work" on many platforms. The real reason for using unions is to create variant records.

union A {
   int i;
   double d;
};

A a[10];    // records in "a" can be either ints or doubles 
a[0].i = 42;
a[1].d = 1.23;

Of course, you also need some sort of discriminator to say what the variant actually contains. And note that in C++ unions are not much use because they can only contain POD types - effectively those without constructors and destructors.


Have you used it thus (like in the question)?? :)
It's a bit pedantic, but I don't quite accept "variant records". That is, I'm sure they were in mind, but if they were a priority why not provide them? "Provide the building block because it might be useful to build other things as well" just seems intuitively more likely. Especially given at least one more application that was probably in mind - memory mapped I/O registers, where the input and output registers (while overlapped) are distinct entities with their own names, types etc.
@Stev314 If that was the use they had in mind, they could have made it not be undefined behaviour.
@Neil: +1 for the first to say about the actual usage without hitting undefined behaviour. I guess they could have made it implementation defined like other type punning operations (reinterpret_cast, etc.). But like I asked, have you used it for type-punning?
@Neil - the memory-mapped register example isn't undefined, the usual endian/etc aside and given a "volatile" flag. Writing to an address in this model doesn't reference the same register as reading the same address. Therefore there is no "what are you reading back" issue as you're not reading back - whatever output you wrote to that address, when you read you're just reading an independent input. The only issue is making sure you read the input side of the union and write the output side. Was common in embedded stuff - probably still is.
T
Totonga

In C it was a nice way to implement something like an variant.

enum possibleTypes{
  eInt,
  eDouble,
  eChar
}


struct Value{

    union Value {
      int iVal_;
      double dval;
      char cVal;
    } value_;
    possibleTypes discriminator_;
} 

switch(val.discriminator_)
{
  case eInt: val.value_.iVal_; break;

In times of litlle memory this structure is using less memory than a struct that has all the member.

By the way C provides

    typedef struct {
      unsigned int mantissa_low:32;      //mantissa
      unsigned int mantissa_high:20;
      unsigned int exponent:11;         //exponent
      unsigned int sign:1;
    } realVal;

to access bit values.


Although both your examples are perfectly defined in the standard; but, hey, using bit fields is sure shot unportable code, isn't it?
No it isn't. As far as I know its widely supported.
Compiler support doesn't translate into portable. The C Book: C (thereby C++) gives no guarantee of the ordering of fields within machine words, so if you do use them for the latter reason, you program will not only be non-portable, it will be compiler-dependent too.
P
Paul R

Although this is strictly undefined behaviour, in practice it will work with pretty much any compiler. It is such a widely used paradigm that any self-respecting compiler will need to do "the right thing" in cases such as this. It's certainly to be preferred over type-punning, which may well generate broken code with some compilers.


Isn't there an endian issue? A relatively easy fix compared with "undefined", but worth taking into account for some projects if so.
M
Matthieu M.

In C++, Boost Variant implement a safe version of the union, designed to prevent undefined behavior as much as possible.

Its performances are identical to the enum + union construct (stack allocated too etc) but it uses a template list of types instead of the enum :)


N
Nick

The behaviour may be undefined, but that just means there isn't a "standard". All decent compilers offer #pragmas to control packing and alignment, but may have different defaults. The defaults will also change depending on the optimisation settings used.

Also, unions are not just for saving space. They can help modern compilers with type punning. If you reinterpret_cast<> everything the compiler can't make assumptions about what you are doing. It may have to throw away what it knows about your type and start again (forcing a write back to memory, which is very inefficient these days compared to CPU clock speed).


J
JoeG

Technically it's undefined, but in reality most (all?) compilers treat it exactly the same as using a reinterpret_cast from one type to the other, the result of which is implementation defined. I wouldn't lose sleep over your current code.


"a reinterpret_cast from one type to the other, the result of which is implementation defined." No, it is not. Implementations do not have to define it, and most do not define it. Also, what would be the allowed implementation defined behaviour of casting some random value to a pointer?
C
Cubbi

For one more example of the actual use of unions, the CORBA framework serializes objects using the tagged union approach. All user-defined classes are members of one (huge) union, and an integer identifier tells the demarshaller how to interpret the union.


p
philcolbourn

Others have mentioned the architecture differences (little - big endian).

I read the problem that since the memory for the variables is shared, then by writing to one, the others change and, depending on their type, the value could be meaningless.

eg. union{ float f; int i; } x;

Writing to x.i would be meaningless if you then read from x.f - unless that is what you intended in order to look at the sign, exponent or mantissa components of the float.

I think there is also an issue of alignment: If some variables must be word aligned then you might not get the expected result.

eg. union{ char c[4]; int i; } x;

If, hypothetically, on some machine a char had to be word aligned then c[0] and c[1] would share storage with i but not c[2] and c[3].


A byte that has to be word aligned? That makes no sense. A byte has no alignment requirement, by definition.
Yes, I probably should have used a better example. Thanks.
@curiousguy: There are many cases where one may wish to have arrays of bytes be word-aligned. If one has many arrays of e.g. 1024 bytes and will frequently wish to copy one to another, having them word aligned may on many systems double the speed of a memcpy() from one to another. Some systems might speculatively align char[] allocations that occur outside of structures/unions for that and other reasons. In the extant example, the assumption that i will overlap all for elements of c[] is non-portable, but that's because there's no guarantee that sizeof(int)==4.
s
supercat

In the C language as it was documented in 1974, all structure members shared a common namespace, and the meaning of "ptr->member" was defined as adding the member's displacement to "ptr" and accessing the resulting address using the member's type. This design made it possible to use the same ptr with member names taken from different structure definitions but with the same offset; programmers used that ability for a variety of purposes.

When structure members were assigned their own namespaces, it became impossible to declare two structure members with the same displacement. Adding unions to the language made it possible to achieve the same semantics that had been available in earlier versions of the language (though the inability to have names exported to an enclosing context may have still necessitated using a find/replace to replace foo->member into foo->type1.member). What was important was not so much that the people who added unions have any particular target usage in mind, but rather that they provide a means by which programmers who had relied upon the earlier semantics, for whatever purpose, should still be able to achieve the same semantics even if they had to use a different syntax to do it.


Appreciate the history lesson, however with the standard defining such and such as undefined, which wasn't the case in the bygone C era where K&R book was the only "standard", one has to be sure in not using it for whatever purpose and enter the UB land.
@legends2k: When the Standard was written, the majority of C implementations treated unions the same way, and such treatment was useful. A few, however, did not, and the authors of the Standard were loath to brand any existing implementations as "non-conforming". Instead, they figured that if implementers didn't need the Standard to tell them to do something (as evidenced by the fact that they were already doing it), leaving it unspecified or undefined would simply preserve the status quo. The notion that it should make things less defined than they were before the Standard was written...
...seems a much more recent innovation. What's particularly sad about all of this is that if compiler writers targeting high-end applications were to figure out how to add useful optimization directives to the language most compilers implemented in the 1990s, rather than gutting features and guarantees that had been supported by "only" 90% of implementations, the result would be a language which could perform better and more reliably than hyper-modern C.
K
Kotauskas

As others mentioned, unions combined with enumerations and wrapped into structs can be used to implement tagged unions. One practical use is to implement Rust's Result<T, E>, which is originally implemented using a pure enum (Rust can hold additional data in enumeration variants). Here is a C++ example:

template <typename T, typename E> struct Result {
    public:
    enum class Success : uint8_t { Ok, Err };
    Result(T val) {
        m_success = Success::Ok;
        m_value.ok = val;
    }
    Result(E val) {
        m_success = Success::Err;
        m_value.err = val;
    }
    inline bool operator==(const Result& other) {
        return other.m_success == this->m_success;
    }
    inline bool operator!=(const Result& other) {
        return other.m_success != this->m_success;
    }
    inline T expect(const char* errorMsg) {
        if (m_success == Success::Err) throw errorMsg;
        else return m_value.ok;
    }
    inline bool is_ok() {
        return m_success == Success::Ok;
    }
    inline bool is_err() {
        return m_success == Success::Err;
    }
    inline const T* ok() {
        if (is_ok()) return m_value.ok;
        else return nullptr;
    }
    inline const T* err() {
        if (is_err()) return m_value.err;
        else return nullptr;
    }

    // Other methods from https://doc.rust-lang.org/std/result/enum.Result.html

    private:
    Success m_success;
    union _val_t { T ok; E err; } m_value;
}

M
Mr. Boy

You can use a a union for two main reasons:

A handy way to access the same data in different ways, like in your example A way to save space when there are different data members of which only one can ever be 'active'

1 Is really more of a C-style hack to short-cut writing code on the basis you know how the target system's memory architecture works. As already said you can normally get away with it if you don't actually target lots of different platforms. I believe some compilers might let you use packing directives also (I know they do on structs)?

A good example of 2. can be found in the VARIANT type used extensively in COM.


r
rob

@bobobobo code is correct as @Joshua pointed out (sadly I'm not allowed to add comments, so doing it here, IMO bad decision to disallow it in first place):

https://en.cppreference.com/w/cpp/language/data_members#Standard_layout tells that it is fine to do so, at least since C++14

In a standard-layout union with an active member of non-union class type T1, it is permitted to read a non-static data member m of another union member of non-union class type T2 provided m is part of the common initial sequence of T1 and T2 (except that reading a volatile member through non-volatile glvalue is undefined).

since in the current case T1 and T2 donate the same type anyway.