Home Union
Post
Cancel

Union

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

image

image

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 안에 넣어서 메모리 공간을 차지하지 않도록 할 수 있다.

This post is written by david61song