A Comprehensive Guide to C Language Unions: Memory Reuse, Bit Manipulation, and Type Punning!

Scan the code to follow Chip Dynamics and say goodbye to “chip” congestion!

A Comprehensive Guide to C Language Unions: Memory Reuse, Bit Manipulation, and Type Punning!A Comprehensive Guide to C Language Unions: Memory Reuse, Bit Manipulation, and Type Punning!Search WeChatA Comprehensive Guide to C Language Unions: Memory Reuse, Bit Manipulation, and Type Punning!Chip DynamicsA Comprehensive Guide to C Language Unions: Memory Reuse, Bit Manipulation, and Type Punning!

Dear “brick movers” of C language, today we will discuss something exciting—unions.

If a structure (Struct) is a “luxury apartment” where each member has its own independent room, then a union is definitely a “shared rental house” where all members squeeze into one room, and whoever occupies the bed last has the final say.

Don’t rush to scroll away! This thing may look “stingy”, but in embedded development and protocol parsing, it is a “memory-saving artifact”. However, if not used properly, it can lead to “data mixing”, more troublesome than a roommate stealing your toothpaste…

What is a Union

A union, also known as a union type, is a special kind of structure that allows multiple members to share the same memory area. For example, placing a short integer variable (2 bytes), a character variable, and a double-precision floating-point variable in the same starting memory address, where the maximum space is determined by the size of the double-precision floating-point variable. The short integer variable, character variable, and double-precision floating-point variable in the union share this memory area. This structure that allows several different variables to share the same memory area is called a union structure.

For example: the following code snippet defines a union.

union UDATA {    short int age;    double score;    char ch;};

The syntax for defining a union is basically the same as that for defining a structure, with the C language keyword for union being “union”. The storage structure of this union is shown in the figure below:

A Comprehensive Guide to C Language Unions: Memory Reuse, Bit Manipulation, and Type Punning!

From the storage structure diagram of the union UDATA, it can be seen that age occupies 2 bytes, ch occupies 1 byte, and score occupies 8 bytes. Age, ch, and score together occupy 8 bytes of memory.

Because the members of a union share the same memory area, a union variable can only use one of its members at a time, and the size of a union structure is determined by the size of its largest member.

Differences Between Union and Structure

Feature/Type

Structure (Struct)

Union (Union)

Memory Allocation

Each member is allocated independent memory space

All members share the same memory space

Memory Size

The total memory size can be considered as the sum of all members’ memory sizes

The total memory size equals the size of the largest member

Data Access

All members can be accessed simultaneously

Only one member can be effectively accessed at a time

Member Storage

Each member has its own memory address

All members have the same starting address

Applicable Scenarios

When multiple different data types need to be stored and accessed simultaneously

When memory needs to be saved and only one member is used at a time

Type Safety

More type-safe, each member has a clear type

Lower type safety, prone to type confusion

  • Structure: Each member has independent memory space, and the total memory occupied by the structure can be considered as the sum of all members’ occupied memory (actual memory usage may be affected by alignment). All members can be accessed simultaneously, and each member has its unique memory address.

  • Union: All members share the same memory, and the memory occupied by the union depends on its largest member. Only one member can be effectively accessed at a time, and accessing different members is actually viewing different interpretations of the same memory area.

Declaring Union Variables

Method 1: Define the union type first, then declare union variables

// Declare union typeunion data {    short m;    float x;    char c;};// Declare union variablesunion data a, b;

First, define a union type union data, which contains three members: short m, float x, and char c. Then, in a separate statement, declare union variables a and b, both of which are of type union data.

Method 2: Define the union type while declaring union variables

// Define union type and declare union variablesunion data {    short m;    float x;    char c;} a, b;

In one line, define the union type union data and simultaneously declare union variables a and b. This method is concise and reduces code volume.

Method 3: The union name can be omitted during definition

// Define union type and declare union variables without giving a union nameunion {    short m;    float x;    char c;} a, b;

When defining the union, omit the union name and directly declare union variables a and b. This method is suitable for cases where the union type does not need to be used multiple times.

Accessing and Assigning Union Members

Like structures, unions also use the dot operator . to access individual members, allowing for assignment and retrieval of values.

Method 1: Declare the union variable first, then assign a value

union data a;a.c = 4;

First, declare a union variable a. Then use the dot operator . to assign the value 4 to member c.

Method 2: Assign a value to any member while declaring the union variable

union data a = {.c = 4};

While declaring the union variable a, use the designated member method to assign the value 4 to member c. This method clearly specifies the member being assigned, improving code readability.

Method 3: Assign a value to the first member while declaring the union variable

union data a = {8};

While declaring the union variable a, assign the value 8 to the first member. This method does not specify the member name, so it can only assign a value to the first member. If the first member of the union is short m, then m will be assigned the value 8.

Typical Applications of Unions

Memory Saving: Scenarios Where Multiple Members Are Not Used Simultaneously

When a program needs to define multiple variables that may be used mutually exclusively (i.e., only one member needs to be focused on at a time), using a union can avoid allocating memory for each member separately, thus saving space.

Example: Sensor Data Reading

Assume a sensor returns different types of data at different times (such as temperature int, status flag char, or error code short), but only one type will be returned at a time. In this case, using a union for storage only requires the memory of the largest member (here, 2 bytes for short).

typedef union {    int temp;       // Temperature value (4 bytes)    char status;    // Status flag (1 byte)    short err_code; // Error code (2 bytes)} SensorData;// Usage: only one member needs to be focused on at a timeSensorData data;data.temp = 25;     // At this time, the union memory stores the temperature value (lower byte may be filled)data.err_code = 0x1234; // Overwrites previous memory, storing the error code

Bit-field Operations: Direct Access to Binary Bits of Data

In hardware programming or protocol parsing, bit manipulation (such as setting a specific bit of a register) is often required. Unions can be combined with bit-fields to divide the same memory block into different bit segments, facilitating read and write of specific binary bits.

Example: Hardware Register Control

Assume a 32-bit hardware control register, where:

  • Bits 0-3: Mode selection (4 bits)

  • Bits 4-7: Interrupt enable (4 bits)

  • Bits 8-31: Reserved (24 bits)

Using a union can directly operate on these bit segments:

typedef union {    uint32_t raw;  // Raw register value (32 bits)    struct {       // Bit-field structure (shares memory with raw)        uint32_t reserved : 24; // Bits 8-31 (reserved)        uint32_t irq_en   : 4;  // Bits 4-7 (interrupt enable)        uint32_t mode     : 4;  // Bits 0-3 (mode selection)    } bits;} ControlReg;// Usage example: set mode to 3, enable interruptControlReg reg;reg.bits.mode = 3;    // Write mode bits (automatically truncated to 4 bits)reg.bits.irq_en = 0xF;// Enable all 4 bits of interrupt// At this time, reg.raw in binary is: 00000000 00000000 00001111 0011 (hexadecimal 0x00000F03)

Type Punning: Different Type Views of the Same Memory

When dealing with binary data (such as network protocol packets or file formats), it is often necessary to parse the same segment of memory according to different data types (for example, viewing a 4-byte int as 4 chars, or viewing a float as an int to see its binary representation). Unions can safely implement this “type punning” (with attention to C standard support).

Example: Checking the Byte Order of an Integer (Big-endian/Little-endian)

By using a union to share memory between an int and a char array, the low-address bytes can be accessed directly:

#include <stdio.h>
typedef union {    int num;      // 4-byte integer    char bytes[4];// Character array storing each byte} IntBytes;
int main() {    IntBytes ib;    ib.num = 0x12345678; // Hexadecimal integer    // Print each byte's hexadecimal value (observe byte order)    for (int i = 0; i < 4; i++) {        printf("bytes[%d] = 0x%02X", i, (unsigned char)ib.bytes[i]);    }    return 0;}
  • If the output is bytes[0]=0x78, bytes[1]=0x56, bytes[2]=0x34, bytes[3]=0x12, it indicates little-endian (low-order byte stored at low address);

  • If the output is in reverse order, it indicates big-endian.

Communication Protocols and Data Parsing

In network communication or file storage, data is often transmitted in binary stream form and needs to be parsed according to different protocols to extract multiple fields from the same data block. Unions can map binary streams to different structures, facilitating field extraction.

Example: Parsing Ethernet Frame Header

The first 14 bytes of the Ethernet frame header contain the destination MAC, source MAC, and type fields. Using a union allows flexible access to these fields:

typedef union {    uint8_t raw[14]; // Raw byte stream (14 bytes)    struct {        uint8_t dst_mac[6];  // Destination MAC address (6 bytes)        uint8_t src_mac[6];  // Source MAC address (6 bytes)        uint16_t type;       // Type field (2 bytes, e.g., 0x0800 for IP protocol)    } fields;} EthHeader;// Usage example: after receiving data from the network, parseEthHeader header;recv(socket, header.raw, 14, 0); // Read raw byte stream into header.rawif (header.fields.type == 0x0800) {    printf("This is an IP packet!");}

Embedded Systems: Hardware Register Mapping

In embedded development, hardware registers are often mapped to memory at specific addresses. Using unions can associate the physical address of a register with logical fields (such as control bits, status bits), facilitating programming.

Example: GPIO Port Registers of STM32

The GPIO port ODR (Output Data Register) of STM32 is a 32-bit register, where each bit controls the output state of a GPIO pin (0=low, 1=high). Using a union can conveniently operate on individual or multiple pins:

typedef union {    volatile uint32_t odr; // Output Data Register (whole access)    struct {        volatile uint32_t pin0 : 1; // Bit 0 controls GPIO0        volatile uint32_t pin1 : 1; // Bit 1 controls GPIO1        // ... other pin bits (commonly 16 bits)        volatile uint32_t reserved : 16; // High 16 bits reserved    } pins;} GPIODATA;// Define register address (assuming GPIOA's ODR address is 0x40020014)#define GPIOA_ODR (*(volatile GPIODATA*)0x40020014)// Usage example: set GPIOA's 5th bit to highGPIOA_ODR.pins.pin5 = 1; 

Conclusion

A union is a special data type where all members share the same memory space. Only one member can be used at a time (the last assigned member is valid), making its core feature memory reuse, suitable for scenarios involving mutually exclusive members, bit operations, and type multi-view parsing. It is particularly common in embedded development, network protocol stacks, and low-level programming such as hardware drivers. However, care must be taken to manage the validity of members to avoid errors caused by data overwriting.

A Comprehensive Guide to C Language Unions: Memory Reuse, Bit Manipulation, and Type Punning!A Comprehensive Guide to C Language Unions: Memory Reuse, Bit Manipulation, and Type Punning!

● Practical C Language Tips: Detailed Explanation of Void Pointers

● Practical C Language Tips: Why Do We Need Double Pointers?

● Essential C Language Development: These Amazing Macros Will Make Your Code Fly!

● C Language const and Pointers: Unlocking Four “Golden Shields” of Pointers

If you find the article good, click Like”, Share”, Recommend”!

Leave a Comment