15

If you have ever used a union in a C/C++ Program let me know. I am really curious to see who in their right minds use unions unironically.

Comments
  • 1
    @jOkEr-jAsE this is a yes? O-O
  • 5
    If properly used, you can build Algebraic Data Types with it (although I would personally only do that in C++, where I could encapsulate it to force proper usage).
    My boss did use it unironically to build something close to an ADT. because he wanted to spare memory on a singleton of a variable size buffer, I shit you not :

    struct Message {
    enum { two_bytes, four_bytes, eight_bytes } var;
    union {
    char two_bytes[2];
    char four_bytes[3]; // protocol changed there
    char eight_bytes[8];
    } buff;
    };

    Every time I look at his code I lose a bit of sanity
  • 2
    @irene Rust uses it for its Algebraic Data Types (rust-enums, not to be confused with c-enums), and they're awesome when used that way
  • 0
    @irene They're awesome, that's how you do error handling in rust. They're basically enums with associated data (or none), the type of which can vary depending on the value of the enum. Best example to me is Result<T, E> which you'd typically unwrap with
    match result {
    Ok(value) => { do_something(value); }
    Err(error) => { handle_error(error); }
    }

    It forces explicit result recovery and thus explicit error handling, but gets translated to this (basically as small a footprint as you can):
    if (result.selector == Ok) {
    do_something(result.value.as_T);
    } else {
    handle_error(result.value.as_E);
    }
  • 1
    @irene tagged union so no unsafe usage
  • 1
    @irene imagine having a color struct and you save the value as a uint32_t or somethinf like that. With a union you could access the value as u32 and the individual colors as an 8bit int.
  • 0
    @irene Basically a tagged union (a struct containing an union and an enum to mark which case of the union is valid), but encapsulated without the need for function calls.

    It's faster and more explicit (considering it's in the typing of the function) than exceptions, because even C++'s "zero cost exceptions" are quite costly when they're thrown ; but if you want to use the returned result, you're gonna HAVE to see the error (you can always just dismiss it, but that's an explicit action), unlike C's return codes which are quite often ignored completely, just because checking a status on a function that usually doesn't fail is annoying.

    So yeah, it's kind of a struct with syntactic sugar, but so are classes (unless you work with JS or Python, in which case everything is basically a dictionary with sugar) x)
  • 0
    @fuckwit If you don't mind being dependent on the endianness of the CPU
  • 0
    Used it once where I needed a uint8_t[4] from the datagram header, but also I needed to compare it as an integer, so I made a union that contains a uint8_t[4] and uint32_t so I didn't have to convert it manually.
    union packet_int {
    uint32_t value;
    uint8_t bytes[4];
    };
  • 0
    I have used it but a long rime ago.

    Except for already named situations I would probably not use it today.
  • 0
    @irene YOU HAVE CODE REVIEWS !? *envious glare*
  • 1
    Unions are regularly used in CUDA programs as in-chip memory is rare and you want to reuse it in CUDA kernels (which are written in C/C++).

    Example:
    https://github.com/NVlabs/cub

    This is a quite common thing.
  • 0
    @jOkEr-jAsE ;D which video was it?
  • 1
    @d4ng3r0u5 yeah ofc you have to keep that in mind
  • 1
    Union are closely related to ADT, so if you're not familiar with fp it looks weird.

    Most popular use case would be Either/Maybe/Option/... type. Which I've seen used in C.
  • 1
    I use them quite often, mostly for serialising/deserialising data. You can have a pixel array with .r/.g/.b and the raw bytes. I like that minimally better than casting the pointer to uint8_t, because the way the data structure is accessed byte-wise, and that it is at all, is clearly outlined in its type definition. If you want to disregard portability completely, they're also really useful with bitflags.

    I thought about using a union for the state of a static UI's views before, as there was only ever one active. Their states could have overlapped in memory because they were reinitialised every time anyway. I didn't have to, in the end, as there was enough memory available.

    My last union usage was actually struct { union { struct; enum } } (which I might actually expand some more) for a device header file. That is more of a proof-of-concept, anyhow.
  • 0
    Also FYI union are also called sum types and are obviously everywhere in haskell.
  • 1
    In my recent project, I had a union for a certain datatype that consists of four bytes. However, copying a uint32_t is faster because of alignment. Also, the value "all bytes 0" is special, and I can check this at union container level instead of checking each byte.
  • 0
    Use it regularly in embedded C to directly access specific bits in an uint32_t var for example (Combined with a struct)
  • 1
    @une-poule that's a bitfield (with struct definition), and you should not use it to access specific bits in the underlying data type because this is non-portable code. The order of bits in a bitfield is not defined by the C standard.

    Use bitmasks or bitmask mased makros instead if you want to access specific bits.
  • 1
    @Fast-Nop yeah but if it is only used within the same system is it still OK ?

    We use it like that :

    typedef union
    {
    struct
    {
    bool bBit1 : 1;
    bool bBit2 : 1;
    ....

    uint32_t ui32Free : 30;
    } sFields;
    uint32_t ui32Bits;
    } uMyUnion_t;

    So you can reset all bits with the ui32Bits field.

    That's legacy code I use in mine, personally I also prefer Mask Macros.
  • 1
    @une-poule if you only use that to reset all bits, or to set all bits, then it's fine because it doesn't matter whether the compiler arranges the bits from MSB to LSB or the other way around.

    What shouldn't be done is e.g. abusing bitfields for mapping register bits, that's where the bit position matters.

    @irene because then you can't zero the whole thing with one load operation, or if you can, it may be slower - depending on the platform.
  • 0
    @irene address pointer casting doesn't work because that would be undefined behaviour. That leaves memset, and with unaligned access, that is slower, depending on the platform.

    Besides, type punning is one of the purpose of unions in C99 and above. Not allowed in C++, however.
Add Comment