🌱 Sự khác nhau giữa Struct vs Union trong C?

🌱 Sự khác nhau giữa Struct vs Union trong C?

    Trong lập trình Embedded C, việc lựa chọn cấu trúc dữ liệu phù hợp có ảnh hưởng lớn đến hiệu suất, mức độ sử dụng bộ nhớ và tính rõ ràng của code. Trong số các cấu trúc cơ bản, StructUnion là hai khái niệm thường xuyên được sử dụng nhưng cũng dễ gây nhầm lẫn.

    Về cơ bản, Struct và Union đều là kiểu dữ liệu User-defined (Người dùng tự định nghĩa) trong C, là kiểu dữ liệu chứa các thành phần dữ liệu khác bên trong do người dùng tự quy định, nhưng cách chúng quản lý bộ nhớ và mục đích sử dụng lại hoàn toàn khác biệt.

    Hai kiểu dữ liệu này được sử dụng khá phổ biến trong các thư viện cho vi điều khiển (ví dụ thư viện STD STM32), và quản lý sự kiện trong các ứng dụng nhúng, IOT.

    Để hiểu rõ về sự khác biệt, bạn cần hiểu về 2 kiểu dữ liệu này trước

Sự Khác Biệt Cốt Lõi Về Bố Cục Bộ Nhớ

    Đây là điểm khác biệt quan trọng nhất và chi phối mọi khía cạnh khác giữa Struct và Union.

Đặc điểm Struct Union
Bộ nhớ được cấp phát Các thành viên được lưu trữ tại các vị trí bộ nhớ liên tiếp và riêng biệt. Mỗi thành viên có vùng bộ nhớ riêng. Các thành viên được lưu trữ tại cùng một vị trí bộ nhớ, chỉ dùng chung một vùng bộ nhớ.
Kích thước Tổng kích thước của tất cả các thành viên cộng với bất kỳ khoảng đệm (padding) nào mà trình biên dịch thêm vào để tối ưu hóa việc truy cập bộ nhớ. Bằng kích thước của thành viên lớn nhất trong Union.
Truy cập Có thể truy cập vào tất cả các thành viên của Struct. Tại một thời điểm, chỉ một thành viên duy nhất (thành viên được cấp phát) có thể được truy cập.
Mục đích Gom nhóm các dữ liệu liên quan với nhau thành một cấu trúc truy cập đồng thời. Tiết kiệm bộ nhớ khi các thành viên không cần thiết phải được lưu trữ cùng lúc, khi muốn xem xét một thành viên hoặc dữ liệu khác nhau với định dạng khác nhau.

Ví dụ Minh họa về bộ nhớ (giả sử int = 4 bytes, float = 4 bytes, char = 1 byte):

struct ExampleStruct {
	int value1; 	// Byte 0-3
	float value2; 	// Byte 4-7
	char status; 	// Byte 8
};
// ExampleStruct size: About 9 bytes (may be larger due to padding)

union ExampleUnion {
	int value1;		// Byte 0-3
	float value2; 	// Byte 0-3
	char status; 	// Byte 0
};
// ExampleUnion size: max(4, 4, 1) = 4 bytes

    Trong ExampleStruct, mỗi thành viên có "ngôi nhà" riêng của nó trong bộ nhớ. Trong ExampleUnion, tất cả các thành viên đều chia sẻ cùng một "ngôi nhà". Khi bạn ghi giá trị vào value1 của ExampleUnion, sau đó ghi vào value2, giá trị của value1 sẽ bị ghi đè.

C struct vs union

Khi Nào Nên Dùng Struct?

    Struct là lựa chọn mặc định và phổ biến nhất khi bạn muốn nhóm các dữ liệu có liên quan lại với nhau. Bạn nên dùng Struct khi:

  • Bạn cần lưu trữ và truy cập đồng thời tất cả các thành viên: Đây là trường hợp sử dụng cơ bản nhất của Struct. Ví dụ: thông tin của một sinh viên (mã số, tên, điểm), tọa độ của một điểm trong không gian (x, y, z).
struct SensorReading {
	uint32_t timestamp;	// Reading time
	float temperature; 	// Temperature
	float humidity; 	// Humidity
	uint8_t sensorId; 	// Sensor ID
};
// All of this information is needed and accessed at the same time for each reading.

  • Tính rõ ràng và an toàn của code là ưu tiên: Với Struct, mỗi thành viên là độc lập, giảm thiểu rủi ro về hành vi không xác định (undefined behavior) do truy cập sai dữ liệu. Mã nguồn trở nên dễ đọc, dễ hiểu và dễ bảo trì hơn.
  • Dữ liệu cần được truyền hoặc lưu trữ dưới dạng một khối: Struct thường được sử dụng làm khuôn mẫu cho các gói tin truyền thông, các bản ghi trong bộ nhớ flash, hoặc các cấu trúc dữ liệu phức tạp hơn.

Khi Nào Nên Dùng Union?

    Union là một công cụ đặc biệt, được sử dụng khi có các yêu cầu cụ thể về tối ưu bộ nhớ hoặc cách thức truy cập dữ liệu. Bạn nên dùng Union khi:

  • Cần tiết kiệm bộ nhớ nghiêm ngặt: Đây là lợi ích lớn nhất của Union. Khi bạn có nhiều loại dữ liệu khác nhau nhưng tại một thời điểm chỉ cần lưu trữ một trong số chúng, Union sẽ giúp giảm đáng kể lượng bộ nhớ sử dụng.
// For example, a message can be either sensor data or a control command,
// but not both.
enum MessageType {
	SENSOR_DATA_MSG,
	CONTROL_CMD_MSG
};

struct Message {
	enum MessageType type;
	union {
		struct {
			float temperature;
			float humidity;
		} sensorData;
		struct {
			uint8_t commandId;
			uint16_t value;
		} controlCmd;
	} payload; // payload
};
// The size of the 'payload' will be the size of the larger structure (sensorData or controlCmd),
// rather than the sum of both if using a struct.
  • Cần truy cập cùng một vùng bộ nhớ dưới nhiều kiểu dữ liệu khác nhau (Type Punning): Union cho phép bạn "đóng vai" dữ liệu theo nhiều kiểu khác nhau, rất hữu ích khi làm việc với giao thức truyền thông, thao tác bit cấp thấp, hoặc chuyển đổi kiểu dữ liệu mà không cần ép kiểu (cast) tường minh.
// Example: Convert a 32-bit integer to 4 separate bytes
union U32Bytes {
	uint32_t value;
	uint8_t bytes[4];
};

void send32BitValue(uint32_t data) {
	union U32Bytes packet;
	packet.value = data; // Assign a 32-bit value
	// Now we can send each byte to the serial port
	send_byte(packet.bytes[0]);
	send_byte(packet.bytes[1]);
	send_byte(packet.bytes[2]);
	send_byte(packet.bytes[3]);
	// Note: Byte order depends on the Endianness of the system
}

  • Tổ chức thanh ghi của Vi điều khiển: Một số thanh ghi điều khiển có các bit/trường bit khác nhau nhưng lại có thể được truy cập như một word hoàn chỉnh để ghi/đọc nhanh hơn. Union giúp mô phỏng cấu trúc này, kết hợp với khái niệm Struct Bitfield.

/**
  \brief  Union type to access the Application Program Status Register (APSR).
 */
typedef union
{
  struct
  {
    uint32_t _reserved0:27;              /*!< bit:  0..26  Reserved */
    uint32_t Q:1;                        /*!< bit:     27  Saturation condition flag */
    uint32_t V:1;                        /*!< bit:     28  Overflow condition code flag */
    uint32_t C:1;                        /*!< bit:     29  Carry condition code flag */
    uint32_t Z:1;                        /*!< bit:     30  Zero condition code flag */
    uint32_t N:1;                        /*!< bit:     31  Negative condition code flag */
  } b;                                   /*!< Structure used for bit  access */
  uint32_t w;                            /*!< Type      used for word access */
} APSR_Type;

Kết luận

    StructUnion là hai cấu trúc dữ liệu cơ bản nhưng có vai trò riêng biệt trong lập trình C, đặc biệt là trong môi trường nhúng.

  • Sử dụng Struct khi bạn cần nhóm các dữ liệu liên quan và truy cập tất cả chúng đồng thời, ưu tiên tính rõ ràng và an toàn của mã nguồn.

  • Sử dụng Union khi bạn cần tiết kiệm bộ nhớ bằng cách cho phép các thành viên chia sẻ cùng một vùng nhớ, hoặc khi bạn muốn truy cập cùng một dữ liệu dưới nhiều kiểu khác nhau. Tuy nhiên, cần hết sức cẩn trọng với việc quản lý trạng thái của Union để tránh hành vi không xác định.

    Việc nắm vững cách hoạt động và mục đích sử dụng của cả hai sẽ giúp bạn viết mã nguồn C cho hệ thống nhúng một cách hiệu quả, tối ưu tài nguyên và dễ bảo trì hơn.

>>>>>> Follow ngay <<<<<<<

Để nhận được những bài học miễn phí mới nhất nhé 😊
Chúc các bạn học tập tốt 😊

Đăng nhận xét

Mới hơn Cũ hơn
//