#[repr(...)] 속성
#![allow(unused)] fn main() { struct Foo { x: i32, y: i16 } }
얼핏 보기엔 Foo
의 크기는 6바이트 (32비트 + 16비트 (= 48비트))가 되야하지만, 실제론 8바이트가 됩니다.
이는 구조체가 메모리 상에 어떻게 저장되는지 알아야하는데, 결론부터 말하자면 6바이트가 아닌 8바이트가 되는 이유는 메모리 상에서 패딩을 적용하기 때문입니다.
패딩은 NULL
데이터가 삽입된것이며, 최대 크기인 x
(32비트 = 4바이트)의 크기와 맞추기위해 y
에 3 바이트(=24비트)의 패딩이 삽입되었습니다. 즉, y
의 크기는 4바이트가 되었습니다.
이렇게 패딩을 넣어주는것이 메모리 정렬인데, 메모리 정렬이 필요한 이유는 CPU
에 있습니다.
컴퓨터는 데이터를 쓰거나 읽을때, 워드(WORD
) 단위로 처리됩니다.
CPU
마다 다르긴 하지만, 1 WORD
는 4바이트를 가집니다. 즉, 컴퓨터는 메모리를 4바이트 단위로 처리하죠.
처음에 본 Foo
구조체를 살펴봅시다.
첫번째 경우: 원본 (6 바이트)
x
32비트 +y
16비트 (= 48비트 = 6바이트)
만약 Foo
의 데이터들이 0x03
부터 0x08
까지 메모리에 저장되어 있다고 가정해봅시다.
00 01 02 03 | 04 05 06 07 | 08 09 0A 0B | 0C 0D 0E 0F
└──────────────────┘
CPU
는 데이터를 처리하려면 WORD
(= 4바이트) 단위로 처리해야합니다. 때문에 CPU
는 총 3번의 메모리 접근을 해야합니다.
두번째 경우: 패딩 적용 (8 바이트)
x
32비트 +y
(16비트 + 패딩 16비트 (= 32비트)) (= 64비트 = 8바이트)
이 경우엔 데이터가 정렬되었고, 때문에 메모리 상에 다음과 같이 저장됩니다.
00 01 02 03 | 04 05 06 07 | 08 09 0A 0B | 0C 0D 0E 0F
└───────────────────────┘
이 경우엔 CPU
는 총 2번의 메모리 접근을 해야합니다.
이처럼 위와같은 상황에선 두번째의 경우가 더 효율적입니다.
즉, 정렬을 위해선 1 WORD
의 배수 크기로 데이터를 저장하면 됩니다. 즉, 구조체의 크기는 구조체에서 가장 큰 데이터의 크기의 배수가 됩니다.
#[repr(..)] 속성
#[repr(..)]
은 구조체 또는 열거형의 메모리 레이아웃을 지정할 수 있는 속성입니다.
메모리 레이아웃은 크기, 정렬, 패딩 등이 포함됩니다.
#[repr(C)]
: C/C++의 레이아웃을 따릅니다. 이는 FFI를 사용할때 유용합니다.#[repr(packed)]
: 패딩을 하지 않습니다. 이는 메모리 절약을 위해 사용되나, 앞서 말한 메모리 정렬이 필요한 상황에선 부정적인 영향이 있을 수 있습니다.#[repr(transparent)]
: 타입의 레이아웃을 필드 타입의 레이아웃으로 설정합니다. 필드는 하나만 제공되어야하며,ZST
(Zero Sized Type
)가 아니어야 합니다.#[repr(align(n))]
: 타입의 정렬을n
으로 설정합니다.n
은 2의 거듭제곱이어야 합니다.#[repr(u*)]
,#[repr(i*)]
: 필드가 없는 열거형의 크기를 지정합니다.*
은8
,16
,32
,64
,128
입니다.
하나의 예시로, #[repr(u8)]
을 사용하여, 열거형의 크기를 1바이트로 지정할 수 있습니다.
#[repr(u8)]
#![allow(unused)] fn main() { #[repr(u8)] enum Color { Red, Green, Blue, } impl From<Color> for u8 { fn from(color: Color) -> u8 { unsafe { std::mem::transmute(color) } } } let color = Color::Red; let value: u8 = color.into(); assert_eq!(value, 0); }
#[repr(transparent)]
#![allow(unused)] fn main() { #[repr(transparent)] struct Wrapper<T>(T); assert_eq!(4, std::mem::size_of::<Wrapper<i32>>()); assert_eq!(1, std::mem::size_of::<Wrapper<u8>>()); }
자세한 내용은 nomicon 참조.