본문 바로가기

프로그래밍/C&C++

Union & Bit-field

반응형

만약 한정된 메모리에서, 한 종류의 값만 표현하는 것이 아니라,
여러 종류의 값을 효율적으로 표현하고 싶으면 어떻게 해야 좋을까요?

Union

#include <cstdint>
#include <iostream>

struct foo
{
	bool a; // 1 byte
	uint32_t b; // 4 byte
};

int main()
{
	std::cout << sizeof(foo) << std::endl; // 8
}
struct foo
a padding b
alignment alignment
1 byte 1 byte 1 byte 1 byte 1 byte 1 byte 1 byte 1 byte

 
위 코드에서 구조체 foo의 필드는 각각 1, 4 byte를 사용해서 값을 표현하고 있어요.
만약 a, b 중에서 항상 한 가지 필드만 사용한다는 가정 하에, 서로 다른 자료형을 표현하기 위해서
구조체 패딩을 포함해 불필요한 메모리를 추가로 할당하는 것은 메모리 측면에서 효율적이지 않아요.
이때 멤버 중에 가장 큰 자료형의 크기의 메모리 공간을 서로 공유해서 사용하면 보다 효율적이지 않을까요?

#include <cstdint>
#include <iostream>

union foo
{
	bool a; // 1 byte
	uint16_t b; // 4 byte
};

int main()
{
	std::cout << sizeof(foo) << std::endl; // 4
}
struct foo
a - - -
b
alignment
1 byte 1 byte 1 byte 1 byte

 
공용체(union)는 이처럼 가장 큰 필드의 자료형의 메모리를 모든 필드가 공유해서 사용해요.
또한 메모리를 공유하는 것 뿐만이 아니라, 구조체 패딩 또한 없기 때문에 메모리 측면에서 매우 우수해요.
다만 공용체에는 심각한 단점이 하나 있는데, 공용체는 언제 어떤 필드가 활성화되어 있는지 스스로 알 수 없어요.

#include <cstdint>
#include <iostream>

union foo
{
	bool a; // 1 byte
	uint16_t b; // 4 byte
};

foo with_a()
{
	return { .a = true };
}

foo with_b()
{
	return { .b = 69 };
}

foo toss()
{
	switch (rand() % 2)
	{
		case 0:
		{
			return with_a();
		}
		default:
		{
			return with_b();
		}
	}
}

int main()
{
	srand(time(0));

	auto coin {toss()};
	
	// coin.a ... is it set?
	// coin.b ... is it set?
}

 
따라서 보통 구조체 내부에 공용체의 어느 필드가 활성화되어 있는지 판별하는 필드를 추가해서 같이 사용해요.

#include <cstdint>
#include <iostream>

struct foo
{
	union data
	{
		bool a;
		uint16_t b;
	};
	enum : uint8_t
	{
		A,
		B,
	}
	tag;
};

foo with_a()
{
	return { .data = { .a = true }, .tag = foo::A };
}

foo with_b()
{
	return { .data = { .b = 69 }, .tag = foo::B };
}

foo toss()
{
	switch (rand() % 2)
	{
		case 0:
		{
			return with_a();
		}
		default:
		{
			return with_b();
		}
	}
}

int main()
{
	srand(time(0));
	
	auto coin {toss()};
	
	switch (coin.tag)
	{
		case foo::A:
		{
			std::cout << "a:" << coin.data.a << std::endl;
			break;
		}
		case foo::B:
		{
			std::cout << "b:" << coin.data.b << std::endl;
			break;
		}
	}
}

 
이처럼 어떤 필드가 활성화 되어 있는지 판별하는 필드를 포함하는 공용체를 tagged union이라고 불러요.

Bit-field

bool 값은 0과 1로 참과 거짓을 표현하기에 1 bit로도 충분히 표현할 수 있지만
메모리 정렬 때문에 1 byte를 전부 차지하는게 비효율적이라고 생각한 적 있으신가요?
또는 특정 표현 범위만 사용한다면, 사용하지 않는 bit를 이용해서 추가 정보를 포함하면 어떨까요?

#include <iostream>

int main()
{
	typedef struct byte
	{
		unsigned int b7 : 1;
		unsigned int b6 : 1;
		unsigned int b5 : 1;
		unsigned int b4 : 1;
		unsigned int b3 : 1;
		unsigned int b2 : 1;
		unsigned int b1 : 1;
		unsigned int b0 : 1;
	};

	auto cast
	{
		[&](byte& foo) -> int
		{
			return
			(
				(foo.b7 << 7)
				|
				(foo.b6 << 6)
				|
				(foo.b5 << 5)
				|
				(foo.b4 << 4)
				|
				(foo.b3 << 3)
				|
				(foo.b2 << 2)
				|
				(foo.b1 << 1)
				|
				(foo.b0 << 0)
			);
		}
	};

	std::cout << cast(byte {0, 0, 0, 0, 0, 0, 0, 1}) << std::endl;
	std::cout << cast(byte {0, 0, 0, 0, 0, 0, 1, 0}) << std::endl;
	std::cout << cast(byte {0, 0, 0, 0, 0, 1, 0, 0}) << std::endl;
	std::cout << cast(byte {0, 0, 0, 0, 1, 0, 0, 0}) << std::endl;
	std::cout << cast(byte {0, 0, 0, 1, 0, 0, 0, 0}) << std::endl;
	std::cout << cast(byte {0, 0, 1, 0, 0, 0, 0, 0}) << std::endl;
	std::cout << cast(byte {0, 1, 0, 0, 0, 0, 0, 0}) << std::endl;
	std::cout << cast(byte {1, 0, 0, 0, 0, 0, 0, 0}) << std::endl;

	return 0;
}

 

또한 주로 bit-wise 연산자를 이용하는 flag 관리를 보다 명료하게 할 수 있어요.

int main()
{
	// bit-field
	typedef struct bf_flags
	{
		bool is_foo : 1;
		bool is_bar : 1;
		bool is_cat : 1;
		bool is_dog : 1;
		bool is_new : 1;
		bool is_old : 1;
		bool is_abc : 1;
		bool is_def : 1;
	};

	bf_flags bf { .is_foo = true, .is_bar = true };

	// bit-wise
	enum bw_flags
	{
		is_foo = 1 << 0,
		is_bar = 1 << 1,
		is_cat = 1 << 2,
		is_dog = 1 << 3,
		is_new = 1 << 4,
		is_old = 1 << 5,
		is_abc = 1 << 6,
		is_def = 1 << 7,
	};

	bw_flags bw {is_foo | is_bar};

	return 0;
}

 
다만 bit-field를 사용할 때는 매우 주의할 사항이 있는데, 표준에서 명확하게 명시하지 않은 까닭에
컴파일러에 따라서 필드를 역순으로 배치하는 경우도 있으며, 따라서 bit-wise에 비해 이식성이 떨어져요.


공용체 및 bit-field는 사실 실무에서 그닥 자주 사용되지는 않아요.
다만, 공용체는 의외로 쉽게 접할 수 있는 곳에서 사용하고있는데, 그것은 바로 문자열이에요.
대박대박ㅋ 다음 글에서는 공용체 및 bit-field와 연계해서 문자열의 구현체를 자세히 살펴보도록 할게요!

Credits

 

Tagged union - Wikipedia

From Wikipedia, the free encyclopedia Data structure used to hold a value that could take on several different, but fixed, types In computer science, a tagged union, also called a variant, variant record, choice type, discriminated union, disjoint union, s

en.wikipedia.org

 

Implementing a variant type from scratch in C++

When programming in C++ we sometimes need heterogeneous containers that can hold more than one type of value (though not more than one at the same time). If our…

ojdip.net

 

Bit-field - cppreference.com

Declares a class data member with explicit size, in bits. Adjacent bit-field members may (or may not) be packed to share and straddle the individual bytes. A bit-field declaration is a class data member declaration which uses the following declarator: iden

en.cppreference.com

 

 
반응형

'프로그래밍 > C&C++' 카테고리의 다른 글

the rule of 0/3/5  (1) 2025.03.18
String & SSO  (3) 2025.02.17
Struct Padding  (8) 2025.02.13
Unicode  (1) 2025.01.19
Metaprogramming  (3) 2024.12.20