The Concept and Declaration of Structures
In the programming paradigm of the C language, structs allow us to combine and store data of different types together, while unions provide a more specialized and efficient mechanism. If we consider a struct to be a "jack of all trades" capable of holding multiple types of data simultaneously, then a union is a "single-purpose expert" that stores only one data type at a specific moment. This design ensures that the memory footprint of a union is typically equal to the size of the smallest basic unit among its declared members, thereby significantly saving storage space.
The definition of a union is simple and intuitive: all variables contained within a single union instance share the same contiguous block of memory space. This means that when you write a value of a specific type into a union, it overwrites any other data previously stored there. This characteristic makes unions particularly suitable for handling data conversion between different formats or scenarios requiring dynamic changes in data types.
From a low-level perspective, the compiler assigns the same starting address to each member. When accessing any member of a union, the program is essentially operating on different interpretations of the same memory region. For example, if we write a 4-byte integer into it, reading that union as a floating-point number involves the CPU merely re-interpreting those 4 bytes according to the IEEE 754 standard, without triggering any data copying or additional memory allocation overhead.
Declaration and Definition of Unions
The first step in defining a union in C is using the union keyword to create a new type name. Subsequently, individual member variables can be listed within curly braces {}. It is important to note that every member within a union must have an explicit data type identifier (such as int, float, char*, etc.).
// Declare a union named data_union
union data_union {
int number; // Integer member
float f_num; // Floating-point member
char str[10]; // Character array member, occupying 10 bytes
};
// Instantiate the union object
data_union my_data;
In the example above, regardless of whether my_data currently stores an integer, a floating-point number, or a string, its total size is typically determined by the largest member (and may be slightly larger than the maximum member due to alignment requirements in most modern compilers). In this specific case, since the character array requires 10 bytes and typically needs 4-byte boundary alignment, the actual memory usage of the union could be 8 or 12 bytes.
Data Write and Read Mechanisms
The core of union operations lies in the "overwrite" characteristic. Before accessing any member, one must know which type of data is intended to be read or written. Once the target member is identified and an assignment operation is performed, values of other types are automatically cleared or completely replaced by the new data.
#include <stdio.h>
int main() {
union data_union u;
// 1. Write integer
u.number = 42;
// At this point, reading the float member will show the result of converting the binary bit pattern
printf("Integer: %d\n", u.number);
printf("Float (raw): %.f\n", u.f_num);
// Note: Directly printing an uninitialized float typically displays as NaN or garbage characters
// 2. Write floating-point number, overwriting integer data
u.f_num = 3.14;
printf("\nInteger (garbage): %d\n", u.number);
printf("Float: %.f\n", u.f_num);
return 0;
}
This code demonstrates how a union dynamically switches data types. When we execute u.f_num = 3.14, the binary bits in memory are modified to represent the format of the floating-point number 3.14. At this point, reading u.number yields a meaningless value (i.e., the integer interpretation of the original float).
Practical Application Scenarios Examples
Unions have widespread applications in embedded systems and network protocol parsing. A typical scenario involves packing data of different types together for transmission, where the receiver selects the correct type for unpacking based on context. Additionally, unions are the preferred solution when compatibility with multiple data formats is required without increasing memory overhead.
// Simulate a simple data transmission structure
union packet_data {
uint8_t raw_byte; // Raw byte stream
int32_t timestamp; // Timestamp (4 bytes)
};
void send_packet(union packet_data *pkt, int type) {
if (type == 't') {
pkt->timestamp = get_current_time();
} else {
pkt->raw_byte = some_sensor_value();
}
}
Through this method, developers can flexibly store and process data from different sources without altering the data structure definition. This not only improves code reusability but also optimizes memory layout efficiency. Mastering the correct usage of unions is a crucial step in deeply understanding the low-level characteristics of the C language.