Bài viết là tài liệu tham khảo cho khóa đào tạo lập trình C/C++ của STDIO Training.
Tìm hiểu và đăng ký học tại đây.
La Kiến Vinh Trong sự phát triển của phần cứng máy tính, nhiều sự hiểu lầm trong việc bỏ đi union và thay bằng sử dụng struct toàn bộ là không đúng. Bài viết này chỉ ra vài trường hợp sử dụng union sẽ giúp cho phát triển ứng dụng tốt hơn.
Nội dung bài viết

Giới thiệu

Bài viết chỉ ra vài trường hợp sử dụng union nhằm phát triển phần mềm tốt hơn. Tùy vào mức độ hay công việc cụ thể mà bạn có thể cần dùng union hoặc không. Cũng như OOP, với quan điểm của tôi union liên quan nhiều đến kinh nghiệm hơn, do đó không thể lấy lý thuyết để nói về union mà phải bằng trải nghiệm.

Đối tượng hướng đến

Bài viết dành cho các bạn khá vững về các vấn đề vùng nhớ và có kinh nghiệm phát triển ứng dụng với C/C++ lâu dài, ngoài ra dành cho các bạn có khả năng hiểu được hệ thống phần mềm.

Nhắc lại khái niệm union

Để biết được union và cách sử dụng của nó bạn có thể đọc bài viết struct Và union trước.

Khi tìm hiểu về union, bạn sẽ làm quen với khái niệm DÙNG CHUNG VÙNG NHỚ, và điều này mang lại nhiều lợi ích.

Cú pháp với union

Cũng như struct, union có dạng khai báo sau

union UnionName
{
	type attribute1;
	type attribute2;
	type attribute3;
	.... ..........;
};

Ví dụ về cú pháp khai báo và sử dụng

// Khai báo 1 union
union Hardware
{
	float	_cpu;
	short	_ram;
	int		_hdd;
};

// Sử dụng union
int main()
{
	Hardware h;
	
	h._cpu = 3.2f;
	h._ram = 256;
	h._hdd = 1024;
	
	return 0;
}

Vấn đề dùng chung vùng nhớ, union Hardware trên sẽ có kích thước của thuộc tính lớn nhất, trong trường hợp này float hoặc int là 4 bytes nên union này có kích thước là 4 bytes.

Vậy với như trên cách lưu trữ này, cả 3 thuộc tính trên sẽ bị ghi đè lên nhau khi 1 trong 3 thuộc tính được gán giá trị. Nếu bạn chưa nghĩ ra ý tưởng sử dụng nó thì union thật tai hại, tuy nhiên nó tồn tại vì các lý do sau đây.

Các trường hợp cần union

Cần tiết kiệm vùng nhớ

Đặt vấn đề, trong 1 tầm vực, 1 biến được cấp phát tĩnh sẽ được khởi tạo khi bắt đầu vào hàm và chỉ hủy khi kết thúc hàm. Giả sử như ta có 1 hàm có 20 dòng codes như giả định bên dưới

void stdio_foo()
{
// (1) Đoạn code cần dùng biến ram
	int ram = 2048; // MB
	
	if (ram > 1)
	{
		printf("STDIO program needs only 1 byte to function.\n");
		printf("STDIO is good.");
	}
		
// (2) Đoạn code không cần dùng biến ram, nhưng cần dùng biến hdd
	int hdd = 1024; // GB
	
	if (hdd > 1)
	{
		printf("STDIO needs less than 100MB to store system.");
	}
}

Với đoạn codes trên, ta cần 8 bytes dành cho 2 biến là ram và hdd, mặc dù ta biết ram chỉ sử dụng 1 nửa hàm, còn nửa còn lại không cần nhưng ta vẫn không thể tận dụng được 4 bytes của ram, bởi vì nó liên quan tới tính bảo trì, dù biến tên là ram để sử dụng như hdd thì sẽ gây hiểu lầm cho tương lai, do đó ta phải tốn thêm 4 bytes cho hdd.

Để giải quyết được vấn đề này sao cho toàn vẹn? Ta sẽ sử dụng năng lực của trình biên dịch và sự hỗ trợ của cú pháp C/C++, cụ thể là ràng buộc bằng union. Điều mong đợi là giữ được codes rõ ràng và chỉ dùng 4 bytes vùng nhớ. Ta cải tiến codes trên như sau

// Khai báo 1 union
union Hardware
{
	short	_ram;
	int		_hdd;
};

void stdio_foo()
{
	Hardware hw;

// (1) Đoạn code cần dùng ram
	hw.ram = 2048; // MB
	
	if (hw.ram > 1)
	{
		printf("STDIO needs only 1 byte to run.\n");
		printf("STDIO is good.");
	}
		
// (2) Đoạn code không cần dùng ram, nhưng cần dùng hdd
	hw.hdd = 1024; // GB

	if (hw.hdd > 1)
	{
		printf("STDIO needs less than 100MB to store system.");
	}
}

Mặc dù đa phần các chương trình đào tạo hiện nay đã lướt qua hoặc bỏ việc đề cập union. Lý do có thể là do bộ nhớ của máy tính ngày càng lớn, và điều này trở nên lạc hậu "tại thời điểm đó". Nhưng những năm gần đây, xu hướng IoT mang chúng ta quay trở lại với hạ tầng điện tử, tôi thấy đó là cơ hội rất lớn để ta quay lại cái gốc của mình. Ví dụ như Arduino Uno R3 dùng ATmega328P, dung lượng bộ nhớ (RAM) chỉ khoảng 2KB (2048 bytes) là rất nhỏ với tư duy phần mềm trước giờ.

Để có được cơ hội mới, ta phải biết tận dụng nhiều kỹ thuật và sử dụng phần cứng hiệu quả hơn. Xem Thông Số Kĩ Thuật Arduino Uno R3 - Các Biến Thể Và Lưu Ý để biết được sự khó khăn này và cách mà union tạo ra cơ hội.

Cần tổ chức chương trình

Xem xét đoạn codes bên dưới

union Vector4
{
	struct {
		float r, g, b, a;
	};
	
	struct {
		float x, y, z, w;
	};
	
	struct {
		float s, t, p, q;
	};
	
	float color[4];
	float position[4];
	float textcoord[4];
};

void rgba_vn()
{
	Vector4 logo_color;
	logo_color.r = 255.0f;
	logo_color.color[0] = 240.0f;
	
	Vector4 monster_position;
	monster_position.x = 100.0f;
	
	// ...
}
THẢO LUẬN
ĐÓNG