Nội dung bài viết
Đăng ký học lập trình C++
Tại STDIO bạn được dạy nền tảng lập trình tốt nhất.
Đăng ký học
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.

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.

Tiền đề bài viết

Trong 3 kiến thức cơ bản về struct - union - enum, có thể nói union khó hiểu không phải về cách sử dụng mà sử dụng để làm gì? Với các nhà phát triển có kinh nghiệm thì đã gặp và giải quyết các vấn đề của họ bằng union, và để cho các bạn mặc dù tốt về kỹ thuật lập trình nhưng đôi lúc chưa có trải nghiệm qua nhiều vấn đề mà giải quyết bằng union có thể biết được tính hữu dụng của nó, tôi chia sẻ vài trường hợp.

Đố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 của Lê Minh Trung.

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à sẽ cảm thấy rất khó hiểu và không biết vì sao, lúc nào cần DÙNG CHUNG VÙNG NHỚ vì nó có vẻ như KHÔNG CẦN THIẾT, DÙNG STRUCT ĐỦ RỒI.

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ề vấn đề dùng chung vùng nhớ như trong bài viết của Lê Minh Trung đề cập, 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 có cái lý của nó. Ta bắt đầu thử vài trường hợp để thấy tính hiệu quả của union.

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;
	
	// ...
}

Khi tôi thiết kế các tính năng cho trang web rgba.vn của STDIO, tôi đã tổ chức lại kiểu dữ liệu Vector4 dùng với nhiều mục đích khác nhau, với 1 kiểu dữ liệu như trên, tôi đã dùng linh hoạt nó như màu sắc, hay tọa độ, hoặc có thể dùng với dạng mảng như mong muốn.

Một điều gì đó hữu dụng hay vô dụng phụ thuộc vào mỗi chúng ta

THẢO LUẬN