For the C language, the supportedcustom structure typesmainly include structures (struct), enumerations (enum), and unions (union). Among these three types, structures are data formats that encapsulate data objects with related functionalities, serving as an important means to achieve code modularization and are the most commonly used data types. Enumerations name ordinary constants, making the code more readable and maintainable. Unions can store multiple types in the same address space, allowing for direct reading of data type conversions, and when combined with structures and bit fields, can be used for special functionalities. This section will introduce these three types, and we will now get to the main topic.
Structures
A structure is a user-defined data format used to encapsulate related functional data. For structures, it is essential to understand the structure format and declaration, initialization, access, and bit field-related content, which will be explained in detail in the following sections.
Structure Format
For a structure, it mainly consists of the keyword struct and {}. Its format is as follows:
// Structure definition// struct_name: Structure name// data_type: Data type// member_name: Member namestruct <struct_name> { <data_type> <member_name>;}; // Example struct com_info { uint16_t length; uint8_t rx_buffer[128];}; // Create structure variable struct com_info com1;
As seen in the example, the structure <span><span>struct com_info</span></span> is defined, which includes two member variables: <span><span>length</span></span> and <span><span>rx_buffer</span></span>. Then, the structure variable <span><span>struct com_info</span></span> variable com1 is created. In C language, when declaring a structure variable, the <span><span>struct keyword</span></span> must be included. If you do not want to do this every time, you can use the typedef keyword for type definition.
// Type definitiontypedef struct com_info struct_data_info; // Create structure variable struct_data_info com2;
Additionally, using typedef allows for the redefinition and use of anonymous structures, as shown in the following example.
// Anonymous structure definition, redefined using typedeftypedef struct { int nums; int flags;} struct_data_info;
Structure Initialization
For initializing structure variables, the following methods can be used.
- Directly initialize when defining the structure variable.
- Initialize after defining the structure variable through assignment.
- If initializing to 0 or a specific value, the memset function can be used for initialization.
Specific examples are shown below.
// Create structure variable and initialize struct_data_info com3 = { .nums = 0, .flags = 0,}; // Initialize after creating structure variable struct_data_info com4;com4.nums = 10;com4.flags = 1; // Use memset function to initialize struct_data_info com5;memset(&com5, 0, sizeof(com5));
Accessing Structures
Accessing structure variables mainly includes the following methods.
- For structure objects, access structure member variables using the
<span><span>.</span></span>operator. - For structure object pointers, access structure member variables using the
<span><span>-></span></span>operator.
Specific examples are shown below.
// Create structure variable struct_data_info com4 = { .nums = 10, .flags = 1,}; // Create structure variable pointer struct_data_info *com4_ptr = &com4; // Access structure member variablesprintf("com4.nums = %d\n", com4.nums); // com4.nums = 10printf("com4.flags = %d\n", com4.flags); // com4.flags = 1 // Access structure member variables pointerprintf("com4_ptr->nums = %d\n", com4_ptr->nums); // com4_ptr->nums = 10printf("com4_ptr->flags = %d\n", com4_ptr->flags); // com4_ptr->flags = 1
Bit Fields
Bit fields are a special data type in C language structures that allow programmers to define and access structure member variables by bits. This feature is particularly important in embedded system development, as it can be used to handle hardware registers and protocol parsing operations.
For bit fields, the format is as follows:
// Bit field definitionstruct <struct_name> { <data_type> <member_name> : <bit_length>; //...} // Example struct bitfield_info { uint32_t nums : 4; uint32_t flags : 4; uint32_t bit_flags : 8; uint32_t reserved : 24;}; // Create structure bit field variable struct bitfield_info bitfield1 = { .nums = 2, .flags = 3, .bit_flags = 4, .reserved = 0,};printf("bitfield1.nums = %d\n", bitfield1.nums);
For bit fields, the declared base type is usually an unsigned integer type, such as uint8_t, uint16_t, uint32_t, etc. It allows programmers to define and access data by bits directly, providing an efficient and intuitive way to handle hardware registers and memory-constrained situations. Additionally, when combined with unions, it can support both bit and byte operations, making it a commonly used data type for low-level hardware access, which will be detailed in the union section.
Enumeration Types
An enumeration is a data type in C language used to name ordinary constants, making the code more readable and maintainable. The definition format of an enumeration is as follows:
// Enumeration definition// enum_name: Enumeration name// member_name: Member nameenum <enum_name> { <member_name> = <value>, <member_name> = <value>,} // Example enum COLOR { COLOR_RED = 0, COLOR_GREEN, COLOR_BLUE,};COLOR color = COLOR_RED;printf("color = %d\n", color); // color = 0 // Use typedef keyword for type definitiontypedef enum COLOR MY_COLOR;MY_COLOR my_color = COLOR_GREEN;printf("my_color = %d\n", my_color); // my_color = 1
As seen in the example, the enumeration <span><span>enum com_status</span></span> is defined, which includes two member variables: <span><span>COM_STATUS_OK</span></span> and <span><span>COM_STATUS_ERROR</span></span><code><span><span>. Then, enumeration variables can be assigned and compared.</span></span>
// Create enumeration variable and assign valueenum COLOR color = COLOR_RED; // Compare enumeration variableif (color == COLOR_RED) { printf("COLOR_RED\n");} else { printf("COLOR_GREEN or COLOR_BLUE\n");}
In C language, the underlying type of an enumeration is based on the integer type, which defaults to int. If enumeration values are not specified, the compiler automatically assigns integers starting from 0. Additionally, C language supports implicit conversion between enumeration types and integers, but in most cases, it is recommended to use explicit conversion for enumeration types.
void func(enum COLOR color) { if (color == COLOR_RED) { printf("COLOR_RED\n"); } else { printf("COLOR_GREEN or COLOR_BLUE\n"); }} // Implicit conversionfunc(COLOR_RED); // COLOR_REDfunc(0); // COLOR_RED
However, this usage is not recommended, and compilers like gcc will generally issue corresponding warnings. If it is necessary to use it, it is recommended to use <span><span>func((enum COLOR)0);</span></span> for explicit type casting to avoid potential errors.
Unions
A union is a data type in C language used to combine multiple data types, sharing the same memory space. The space occupied by a union is determined by the largest variable it contains. The definition format of a union is as follows:
// Union definitionunion <union_name> { <data_type> <member_name>; //...} // Example union test_union { int a; short b;};union test_union union1;
The main purpose of a union is to access data segments internally by sharing memory addresses; this provides a more convenient way to parse certain variables. The union type, when combined with structures, can implement protocol parsing functions as follows.
union protocol_info { struct { uint8_t head; uint8_t cmd; uint16_t len; uint8_t data[]; }; uint8_t data[16];};__packed union protocol_info info;uint8_t data[] = {0xAA, 0x01, 0x03, 0x01, 0x02, 0x03};memcpy(info.data, data, sizeof(data));printf("info.head = 0x%02X\n", info.head);printf("info.cmd = 0x%02X\n", info.cmd);printf("info.len = 0x%04X\n", info.len);
The above structure can easily parse protocol data and print out information such as protocol header, command, and length. However, it is important to consider the structure’s alignment mechanism and the endianness of communication data on both sides. The <span><span>__packed</span></span> keyword should be used to pack the data to ensure that it does not get corrupted due to padding. Additionally, testing the endianness of chips is also a common application of unions, as shown in the following example.
#include <stdio.h> // Union definitiontypedef union { short s; int i; }UNION_VAL; // Union variable UNION_VAL val;val.i = 0x12345678; if(val.s == 0x5678) { printf("Little-endian mode\n");} else { printf("Big-endian mode\n");}
Of course, the most common scenario for unions is still to work with bit fields to operate on hardware registers, as shown in the following example.
typedef union { struct { uint32_t bit0:3; uint32_t bit1:2; uint32_t bit2:2; uint32_t bit3:4; uint32_t bit4:4; uint32_t bit5:3; uint32_t bit6:5; uint32_t reserved:8; } bits; uint32_t data;} reg_t;reg_t reg;reg.data = 0x12345678;printf("reg.bits.bit0 = %d\n", reg.bits.bit0);printf("reg.bits.bit1 = %d\n", reg.bits.bit1);reg.bits.bit0 = 0x7;printf("reg.data = 0x%08X\n", reg.data);
As seen, through unions and bit fields, we can conveniently operate on hardware registers.
Summary
In this section, we introduced commonly used data types in C language, including structures, bit fields, enumerations, and unions. By combining different data types, we can create custom data structures that facilitate modular management, improving code readability and maintainability. Mastering these three types is also key to achieving high-quality C language code, which is worth exploring and mastering.