Intro
union
키워드 (공용체)를 C에서 크게 쓸 일이 없었는데, 이번에 linux kernel을 주로 공부하면서 자주 등장해서 이번 기회에 정리하고자 한다. union을 이용하면 메모리 공간을 조금 더 효율적으로 쓸 수 있는데, linux kernel을 공부하기 전까지는 겨우 몇 바이트가 중요한지 잘 이해가 가지 않았다.
하지만, 페이징을 관리하는 자료구조나, 여러 커널의 자료구조는 이러한 entry가 늘어날수록 2-3 byte를 절약하는 것이 큰 차이를 가져오기 때문에, 쓰는 것으로 추측한다. 일단 union에 대해서 알아보도록 하자.
Union vs Struct
struct
(구조체)와 union
(공용체)를 그림으로 나타내어 보면 다음과 같다.
1
2
3
4
5
struct something{
int num;
short short_num;
char c;
}
1
2
3
4
5
union something{
int num;
short short_num;
char c;
}
size of union
union
자료형의 가장 큰 크기는 멤버 변수의 크기 중 가장 큰 크기로 정의된다. 위의 union somthing
의 크기 (sizeof(something)
)은 4이다. (int
가 보통 4바이트이므로)
잘 알고 있겠지만,
sizeof
는 함수가 아니라 연산자다!
union의 특징
구조체와는 달리 공용체는 멤버 중에서 가장 큰 자료형의 공간을 공유한다. 따라서 어느 한 멤버에 값을 저장하면 나머지 멤버의 값은 사용할 수 없는 상태가 된다. 그래서 공용체의 멤버는 한 번에 하나씩 쓰면 값을 정상적으로 사용할 수 있다.
구조체와의 차이점은, 이러한 특징으로 인해 조금 더 메모리를 절약할 수 있다는 점이다.
usages
공용체는 여러 멤버에 동시에 접근하지 않는 경우 같은 메모리 레이아웃에 멤버를 모아둘 때 사용한다. 특히 공용체는 임베디드 시스템이나 커널 모드 디바이스 드라이버 등에서 주로 사용하며 보통은 거의 쓰지 않는다.
simple example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <stdio.h>
struct something{
int num;
int num2;
short short_num;
char me;
};
union something_union{
int num;
int num2;
short short_num;
char me;
};
int main(){
struct something a;
union something_union b;
char *byte_p;
printf("sizeof struct : %ld, sizeof union : %ld\n", sizeof(a), sizeof(b));
b.me = 'a';
b.num = 0x13445511;
byte_p = (char *) &b;
for(int i = 0 ; i < sizeof(b) ; i ++){
printf("[0x%x]", *(byte_p + i));
}
}
실행 결과는 다음과 같다.
1
2
sizeof struct : 12, sizeof union : 4
[0x11][0x55][0x44][0x13]%
byte pointer를 이용해 union 자료형을 직접 확인해 보면, char me
에 ‘a’라는 문자 하나를 저장해도, 다른 변수에 값을 쓰면 사라지는 것을 확인할 수 있다.
숫자가 거꾸로 보이는 것은 현재 시스템 (Apple MacBook m1 pro)가 little endian
을 사용하고 있어서이다.
linux kernel
물리 메모리의 각 프레임을 나타내는 자료구조 struct page
의 일부를 가져왔다. (6.9.7 버전)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
struct page {
unsigned long flags;
union {
struct {
union {
struct list_head lru;
struct {
void *__filler;
unsigned int mlock_count;
};
struct list_head buddy_list;
struct list_head pcp_list;
};
struct address_space *mapping;
union {
pgoff_t index;
unsigned long share;
};
unsigned long private;
};
struct {
unsigned long pp_magic;
struct page_pool *pp;
unsigned long _pp_mapping_pad;
unsigned long dma_addr;
atomic_long_t pp_ref_count;
};
struct {
unsigned long compound_head;
};
struct {
struct dev_pagemap *pgmap;
void *zone_device_data;
};
struct rcu_head rcu_head;
};
union {
atomic_t _mapcount;
unsigned int page_type;
};
atomic_t _refcount;
#ifdef CONFIG_MEMCG
unsigned long memcg_data;
#endif
#if defined(WANT_PAGE_VIRTUAL)
void *virtual;
#endif
#ifdef LAST_CPUPID_NOT_IN_PAGE_FLAGS
int _last_cpupid;
#endif
#ifdef CONFIG_KMSAN
struct page *kmsan_shadow;
struct page *kmsan_origin;
#endif
} _struct_page_alignment;
매우 구조가 복잡해 보이지만, struct page
안에 쓰지 않는 필드들은 union
안에 넣어서 메모리 공간을 차지하지 않도록 할 수 있다.